001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.spring.generator;
018    
019    import java.io.File;
020    import java.io.FileWriter;
021    import java.io.IOException;
022    import java.io.PrintWriter;
023    import java.util.Iterator;
024    import java.util.List;
025    
026    /**
027     * @author Dain Sundstrom
028     * @version $Id$
029     * @since 1.0
030     */
031    public class XsdGenerator implements GeneratorPlugin {
032        private final File destFile;
033        private LogFacade log;
034    
035        public XsdGenerator(File destFile) {
036            this.destFile = destFile;
037        }
038    
039        public void generate(NamespaceMapping namespaceMapping) throws IOException {
040            // TODO can only handle 1 schema document so far...
041            File file = destFile;
042            log.log("Generating XSD file: " + file + " for namespace: " + namespaceMapping.getNamespace());
043            PrintWriter out = new PrintWriter(new FileWriter(file));
044            try {
045                generateSchema(out, namespaceMapping);
046            } finally {
047                out.close();
048            }
049        }
050    
051        public void generateSchema(PrintWriter out, NamespaceMapping namespaceMapping) {
052            out.println("<?xml version='1.0'?>");
053            out.println("<!-- NOTE: this file is autogenerated by Apache XBean -->");
054            out.println();
055            out.println("<xs:schema elementFormDefault='qualified'");
056            out.println("           targetNamespace='" + namespaceMapping.getNamespace() + "'");
057            out.println("           xmlns:xs='http://www.w3.org/2001/XMLSchema'");
058            out.println("           xmlns:tns='" + namespaceMapping.getNamespace() + "'>");
059    
060            for (Iterator iter = namespaceMapping.getElements().iterator(); iter.hasNext();) {
061                ElementMapping element = (ElementMapping) iter.next();
062                generateElementMapping(out, namespaceMapping, element);
063            }
064    
065            out.println();
066            out.println("</xs:schema>");
067        }
068    
069        private void generateElementMapping(PrintWriter out, NamespaceMapping namespaceMapping, ElementMapping element) {
070            out.println();
071            out.println("  <!-- element for type: " + element.getClassName() + " -->");
072    
073            String localName = element.getElementName();
074    
075            out.println("  <xs:element name='" + localName + "'>");
076    
077            if (!isEmptyString(element.getDescription())) {
078                out.println("    <xs:annotation>");
079                out.println("      <xs:documentation><![CDATA[");
080                out.println("        " + element.getDescription());
081                out.println("      ]]></xs:documentation>");
082                out.println("    </xs:annotation>");
083            }
084    
085            out.println("    <xs:complexType>");
086    
087            int complexCount = 0;
088            for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
089                AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
090                if (!namespaceMapping.isSimpleType(attributeMapping.getType())) {
091                    complexCount++;
092                }
093            }
094            if (complexCount > 0) {
095                out.println("      <xs:sequence>");
096                for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
097                    AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
098                    if (!namespaceMapping.isSimpleType(attributeMapping.getType())) {
099                        generateElementMappingComplexProperty(out, namespaceMapping, attributeMapping);
100                    }
101                }
102                out.println("        <xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>");
103                out.println("      </xs:sequence>");
104            }
105    
106            for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
107                AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
108                if (namespaceMapping.isSimpleType(attributeMapping.getType())) {
109                    generateElementMappingSimpleProperty(out, attributeMapping);
110                } else if (!attributeMapping.getType().isCollection()) {
111                    generateElementMappingComplexPropertyAsRef(out, attributeMapping);
112                }
113            }
114            generateIDAttributeMapping(out, namespaceMapping, element);
115    
116            out.println("      <xs:anyAttribute namespace='##other' processContents='lax'/>");
117            out.println("    </xs:complexType>");
118            out.println("  </xs:element>");
119            out.println();
120        }
121    
122        private boolean isEmptyString(String str) {
123            if (str == null) {
124                return true;
125            }
126            for (int i = 0; i < str.length(); i++) {
127                if (!Character.isWhitespace(str.charAt(i))) {
128                    return false;
129                }
130            }
131            return true;
132        }
133    
134        private void generateIDAttributeMapping(PrintWriter out, NamespaceMapping namespaceMapping, ElementMapping element) {
135            for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
136                AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
137                if ("id".equals(attributeMapping.getAttributeName())) {
138                    return;
139                }
140            }
141            out.println("      <xs:attribute name='id' type='xs:ID'/>");
142        }
143    
144        private void generateElementMappingSimpleProperty(PrintWriter out, AttributeMapping attributeMapping) {
145            // types with property editors need to be xs:string in the schema to validate
146            String type = attributeMapping.getPropertyEditor() != null ? 
147                            Utils.getXsdType(Type.newSimpleType(String.class.getName())) : Utils.getXsdType(attributeMapping.getType());
148            if (!isEmptyString(attributeMapping.getDescription())) {
149                out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='" + type + "'>");
150                out.println("        <xs:annotation>");
151                out.println("          <xs:documentation><![CDATA[");
152                out.println("            " + attributeMapping.getDescription());
153                out.println("          ]]></xs:documentation>");
154                out.println("        </xs:annotation>");
155                out.println("      </xs:attribute>");
156            } else {
157                out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='" + type + "'/>");
158            }
159        }
160    
161        private void generateElementMappingComplexPropertyAsRef(PrintWriter out, AttributeMapping attributeMapping) {
162            if (!isEmptyString(attributeMapping.getDescription())) {
163                out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='xs:string'>");
164                out.println("        <xs:annotation>");
165                out.println("          <xs:documentation><![CDATA[");
166                out.println("            " + attributeMapping.getDescription());
167                out.println("          ]]></xs:documentation>");
168                out.println("        </xs:annotation>");
169                out.println("      </xs:attribute>");
170            } else {
171                out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='xs:string'/>");
172            }
173        }
174    
175        private void generateElementMappingComplexProperty(PrintWriter out, NamespaceMapping namespaceMapping, AttributeMapping attributeMapping) {
176            Type type = attributeMapping.getType();
177            List types;
178            if (type.isCollection()) {
179                types = Utils.findImplementationsOf(namespaceMapping, type.getNestedType());
180            } else {
181                types = Utils.findImplementationsOf(namespaceMapping, type);
182            }
183            String maxOccurs = type.isCollection() || "java.util.Map".equals(type.getName()) ? "unbounded" : "1";
184    
185            out.println("        <xs:element name='" + attributeMapping.getAttributeName() + "' minOccurs='0' maxOccurs='1'>");
186            if (!isEmptyString(attributeMapping.getDescription())) {
187                out.println("          <xs:annotation>");
188                out.println("            <xs:documentation><![CDATA[");
189                out.println("              " + attributeMapping.getDescription());
190                out.println("            ]]></xs:documentation>");
191                out.println("          </xs:annotation>");
192            }
193            out.println("          <xs:complexType>");
194            if (types.isEmpty()) {
195                // We don't know the type because it's generic collection.  Allow folks to insert objets from any namespace
196                out.println("            <xs:sequence minOccurs='0' maxOccurs='" + maxOccurs + "'><xs:any minOccurs='0' maxOccurs='unbounded'/></xs:sequence>");
197            } else {
198                out.println("            <xs:choice minOccurs='0' maxOccurs='" + maxOccurs + "'>");
199                for (Iterator iterator = types.iterator(); iterator.hasNext();) {
200                    ElementMapping element = (ElementMapping) iterator.next();
201                    out.println("              <xs:element ref='tns:" + element.getElementName() + "'/>");
202                }
203                out.println("              <xs:any namespace='##other'/>");
204                out.println("            </xs:choice>");
205            }
206            out.println("          </xs:complexType>");
207            out.println("        </xs:element>");
208        }
209    
210        public LogFacade getLog() {
211            return log;
212        }
213    
214        public void setLog(LogFacade log) {
215            this.log = log;
216        }
217    
218    }