1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.xsdc.java;
18 
19 import com.android.xsdc.CodeWriter;
20 import com.android.xsdc.FileSystem;
21 import com.android.xsdc.XmlSchema;
22 import com.android.xsdc.XsdConstants;
23 import com.android.xsdc.tag.*;
24 
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 
33 import javax.xml.namespace.QName;
34 
35 public class JavaCodeGenerator {
36     private XmlSchema xmlSchema;
37     private String packageName;
38     private Map<String, JavaSimpleType> javaSimpleTypeMap;
39     private boolean writer;
40     private boolean showNullability;
41     private boolean generateHasMethod;
42     private boolean useHexBinary;
43     private boolean booleanGetter;
44 
JavaCodeGenerator(XmlSchema xmlSchema, String packageName, boolean writer, boolean showNullability, boolean generateHasMethod, boolean booleanGetter)45     public JavaCodeGenerator(XmlSchema xmlSchema, String packageName, boolean writer,
46             boolean showNullability, boolean generateHasMethod, boolean booleanGetter)
47             throws JavaCodeGeneratorException {
48         this.xmlSchema = xmlSchema;
49         this.packageName = packageName;
50         this.writer = writer;
51         this.showNullability = showNullability;
52         this.generateHasMethod = generateHasMethod;
53         this.booleanGetter = booleanGetter;
54         useHexBinary = false;
55 
56         // class naming validation
57         {
58             Set<String> nameSet = new HashSet<>();
59             nameSet.add("XmlParser");
60             for (XsdType type : xmlSchema.getTypeMap().values()) {
61                 if ((type instanceof XsdComplexType) || (type instanceof XsdRestriction &&
62                         ((XsdRestriction)type).getEnums() != null)) {
63                     String name = Utils.toClassName(type.getName());
64                     if (nameSet.contains(name)) {
65                         throw new JavaCodeGeneratorException(
66                                 String.format("duplicate class name : %s", name));
67                     }
68                     nameSet.add(name);
69                 }
70             }
71             for (XsdElement element : xmlSchema.getElementMap().values()) {
72                 XsdType type = element.getType();
73                 if (type.getRef() == null && type instanceof XsdComplexType) {
74                     String name = Utils.toClassName(element.getName());
75                     if (nameSet.contains(name)) {
76                         throw new JavaCodeGeneratorException(
77                                 String.format("duplicate class name : %s", name));
78                     }
79                     nameSet.add(name);
80                 }
81             }
82         }
83 
84         javaSimpleTypeMap = new HashMap<>();
85         for (XsdType type : xmlSchema.getTypeMap().values()) {
86             if (type instanceof XsdSimpleType) {
87                 XsdType refType = new XsdType(null, new QName(type.getName()));
88                 parseSimpleType(refType, true);
89             }
90         }
91     }
92 
print(FileSystem fs)93     public void print(FileSystem fs)
94             throws JavaCodeGeneratorException, IOException {
95         for (XsdType type : xmlSchema.getTypeMap().values()) {
96             if (type instanceof XsdComplexType) {
97                 String name = Utils.toClassName(type.getName());
98                 XsdComplexType complexType = (XsdComplexType) type;
99                 try (CodeWriter out = new CodeWriter(fs.getPrintWriter(name + ".java"))) {
100                     out.printf("package %s;\n\n", packageName);
101                     printClass(out, name, complexType, "");
102                 }
103             } else if (type instanceof XsdRestriction &&
104                     ((XsdRestriction)type).getEnums() != null) {
105                 String name = Utils.toClassName(type.getName());
106                 XsdRestriction restrictionType = (XsdRestriction) type;
107                 try (CodeWriter out = new CodeWriter(fs.getPrintWriter(name + ".java"))) {
108                     out.printf("package %s;\n\n", packageName);
109                     printEnumClass(out, name, restrictionType);
110                 }
111             }
112         }
113         for (XsdElement element : xmlSchema.getElementMap().values()) {
114             XsdType type = element.getType();
115             if (type.getRef() == null && type instanceof XsdComplexType) {
116                 String name = Utils.toClassName(element.getName());
117                 XsdComplexType complexType = (XsdComplexType) type;
118                 try (CodeWriter out = new CodeWriter(fs.getPrintWriter(name + ".java"))) {
119                     out.printf("package %s;\n\n", packageName);
120                     printClass(out, name, complexType, "");
121                 }
122             }
123         }
124         try (CodeWriter out = new CodeWriter(fs.getPrintWriter("XmlParser.java"))) {
125             printXmlParser(out);
126         }
127         if (writer) {
128             try (CodeWriter out = new CodeWriter(fs.getPrintWriter("XmlWriter.java"))) {
129                 printXmlWriter(out);
130             }
131         }
132         if (useHexBinary) {
133             try (CodeWriter out = new CodeWriter(fs.getPrintWriter("HexBinaryHelper.java"))) {
134                 printHexBinaryHelper(out);
135             }
136         }
137     }
138 
printEnumClass(CodeWriter out, String name, XsdRestriction restrictionType)139     private void printEnumClass(CodeWriter out, String name, XsdRestriction restrictionType)
140             throws JavaCodeGeneratorException {
141         if (restrictionType.isDeprecated()) {
142             out.printf("@java.lang.Deprecated\n");
143         }
144         out.printf("public enum %s {", name);
145         List<XsdEnumeration> enums = restrictionType.getEnums();
146 
147         for (XsdEnumeration tag : enums) {
148             if (tag.isDeprecated()) {
149                 out.printf("@java.lang.Deprecated\n");
150             }
151             String value = tag.getValue();
152             out.printf("\n%s(\"%s\"),", Utils.toEnumName(value), value);
153         }
154         out.printf(";\n\n");
155         out.printf("private final String rawName;\n\n");
156         out.printf("%s(%sString rawName) {\n"
157                 + "this.rawName = rawName;\n"
158                 + "}\n\n", name, getDefaultNullability(Nullability.NON_NULL));
159         out.printf("public %sString getRawName() {\n"
160                 + "return rawName;\n"
161                 + "}\n\n", getDefaultNullability(Nullability.NON_NULL));
162 
163         out.printf("static %s%s fromString(%sString rawString) {\n"
164                 + "for (%s _f : values()) {\n"
165                 + "if (_f.getRawName().equals(rawString)) {\n"
166                 + "return _f;\n"
167                 + "}\n"
168                 + "}\n"
169                 + "throw new IllegalArgumentException(rawString);\n"
170                 + "}\n\n", getDefaultNullability(Nullability.NULLABLE), name,
171                 getDefaultNullability(Nullability.NON_NULL), name);
172 
173         if (writer) {
174             out.printf("@Override\n"
175                     + "public %sString toString() {\n"
176                     + "return rawName;\n"
177                     + "}\n", getDefaultNullability(Nullability.NON_NULL));
178         }
179         out.println("}");
180     }
181 
printClass(CodeWriter out, String name, XsdComplexType complexType, String nameScope)182     private void printClass(CodeWriter out, String name, XsdComplexType complexType,
183             String nameScope) throws JavaCodeGeneratorException {
184         assert name != null;
185         // need element, attribute name duplicate validation?
186 
187         String baseName = getBaseName(complexType);
188         JavaSimpleType valueType = (complexType instanceof XsdSimpleContent) ?
189                 getValueType((XsdSimpleContent) complexType, false) : null;
190 
191         String finalString = getFinalString(complexType.isFinalValue());
192         if (complexType.isDeprecated()) {
193             out.printf("@java.lang.Deprecated\n");
194         }
195         if (nameScope.isEmpty()) {
196             out.printf("public%s class %s ", finalString, name);
197         } else {
198             out.printf("public%s static class %s ", finalString, name);
199         }
200         if (baseName != null) {
201             out.printf("extends %s {\n", baseName);
202         } else {
203             out.println("{");
204         }
205 
206         // parse types for elements and attributes
207         List<JavaType> elementTypes = new ArrayList<>();
208         List<XsdElement> elements = new ArrayList<>();
209         elements.addAll(getAllElements(complexType.getGroup()));
210         elements.addAll(complexType.getElements());
211 
212         for (XsdElement element : elements) {
213             JavaType javaType;
214             XsdElement elementValue = resolveElement(element);
215             if (element.getRef() == null && element.getType().getRef() == null
216                     && element.getType() instanceof XsdComplexType) {
217                 // print inner class for anonymous types
218                 String innerName = Utils.toClassName(getElementName(element));
219                 XsdComplexType innerType = (XsdComplexType) element.getType();
220                 String innerNameScope = nameScope + name + ".";
221                 printClass(out, innerName, innerType, innerNameScope);
222                 out.println();
223                 javaType = new JavaComplexType(innerNameScope + innerName);
224             } else {
225                 javaType = parseType(elementValue.getType(), getElementName(elementValue));
226             }
227             elementTypes.add(javaType);
228         }
229         List<JavaSimpleType> attributeTypes = new ArrayList<>();
230         List<XsdAttribute> attributes =  new ArrayList<>();
231         for (XsdAttributeGroup attributeGroup : complexType.getAttributeGroups()) {
232             attributes.addAll(getAllAttributes(resolveAttributeGroup(attributeGroup)));
233         }
234         attributes.addAll(complexType.getAttributes());
235 
236         for (XsdAttribute attribute : attributes) {
237             XsdType type = resolveAttribute(attribute).getType();
238             attributeTypes.add(parseSimpleType(type, false));
239         }
240 
241         // print member variables
242         for (int i = 0; i < elementTypes.size(); ++i) {
243             JavaType type = elementTypes.get(i);
244             XsdElement element = elements.get(i);
245             XsdElement elementValue = resolveElement(element);
246             String typeName = element.isMultiple() ? String.format("java.util.List<%s>",
247                     type.getNullableName()) : type.getNullableName();
248             out.printf("%sprivate %s %s;\n", getNullabilityString(element.getNullability()),
249                     typeName, Utils.toVariableName(getElementName(elementValue)));
250         }
251         for (int i = 0; i < attributeTypes.size(); ++i) {
252             JavaType type = attributeTypes.get(i);
253             XsdAttribute attribute = resolveAttribute(attributes.get(i));
254             out.printf("%sprivate %s %s;\n", getNullabilityString(attribute.getNullability()),
255                     type.getNullableName(), Utils.toVariableName(attribute.getName()));
256         }
257         if (valueType != null) {
258             out.printf("private %s value;\n", valueType.getName());
259         }
260 
261         // print getters and setters
262         for (int i = 0; i < elementTypes.size(); ++i) {
263             JavaType type = elementTypes.get(i);
264             XsdElement element = elements.get(i);
265             XsdElement elementValue = resolveElement(element);
266             printGetterAndSetter(out, type, Utils.toVariableName(getElementName(elementValue)),
267                     element.isMultiple(), element);
268         }
269         for (int i = 0; i < attributeTypes.size(); ++i) {
270             JavaType type = attributeTypes.get(i);
271             XsdAttribute attribute = resolveAttribute(attributes.get(i));
272             printGetterAndSetter(out, type, Utils.toVariableName(attribute.getName()), false,
273                     attribute);
274         }
275         if (valueType != null) {
276             printGetterAndSetter(out, valueType, "value", false, null);
277         }
278 
279         out.println();
280         printParser(out, nameScope + name, complexType);
281         if (writer) {
282             printWriter(out, name, complexType);
283         }
284 
285         out.println("}");
286     }
287 
printParser(CodeWriter out, String name, XsdComplexType complexType)288     private void printParser(CodeWriter out, String name, XsdComplexType complexType)
289             throws JavaCodeGeneratorException {
290         JavaSimpleType baseValueType = (complexType instanceof XsdSimpleContent) ?
291                 getValueType((XsdSimpleContent) complexType, true) : null;
292         List<XsdElement> allElements = new ArrayList<>();
293         List<XsdAttribute> allAttributes = new ArrayList<>();
294         stackComponents(complexType, allElements, allAttributes);
295 
296         // parse types for elements and attributes
297         List<JavaType> allElementTypes = new ArrayList<>();
298         for (XsdElement element : allElements) {
299             XsdElement elementValue = resolveElement(element);
300             JavaType javaType = parseType(elementValue.getType(), elementValue.getName());
301             allElementTypes.add(javaType);
302         }
303         List<JavaSimpleType> allAttributeTypes = new ArrayList<>();
304         for (XsdAttribute attribute : allAttributes) {
305             XsdType type = resolveAttribute(attribute).getType();
306             allAttributeTypes.add(parseSimpleType(type, false));
307         }
308 
309         out.printf("static %s%s read(%sorg.xmlpull.v1.XmlPullParser parser) " +
310                 "throws org.xmlpull.v1.XmlPullParserException, java.io.IOException, " +
311                 "javax.xml.datatype.DatatypeConfigurationException {\n",
312                 getDefaultNullability(Nullability.NON_NULL), name,
313                 getDefaultNullability(Nullability.NON_NULL));
314 
315         out.printf("%s instance = new %s();\n"
316                 + "String raw = null;\n", name, name);
317         for (int i = 0; i < allAttributes.size(); ++i) {
318             JavaType type = allAttributeTypes.get(i);
319             XsdAttribute attribute = resolveAttribute(allAttributes.get(i));
320             String variableName = Utils.toVariableName(attribute.getName());
321             out.printf("raw = parser.getAttributeValue(null, \"%s\");\n"
322                     + "if (raw != null) {\n", attribute.getName());
323             out.print(type.getParsingExpression());
324             out.printf("instance.set%s(value);\n"
325                     + "}\n", Utils.capitalize(variableName));
326         }
327 
328         if (baseValueType != null) {
329             out.print("raw = XmlParser.readText(parser);\n"
330                     + "if (raw != null) {\n");
331             out.print(baseValueType.getParsingExpression());
332             out.print("instance.setValue(value);\n"
333                     + "}\n");
334         } else if (!allElements.isEmpty()) {
335             out.print("int outerDepth = parser.getDepth();\n"
336                     + "int type;\n"
337                     + "while ((type=parser.next()) != org.xmlpull.v1.XmlPullParser.END_DOCUMENT\n"
338                     + "        && type != org.xmlpull.v1.XmlPullParser.END_TAG) {\n"
339                     + "if (parser.getEventType() != org.xmlpull.v1.XmlPullParser.START_TAG) "
340                     + "continue;\n"
341                     + "String tagName = parser.getName();\n");
342             for (int i = 0; i < allElements.size(); ++i) {
343                 JavaType type = allElementTypes.get(i);
344                 XsdElement element = allElements.get(i);
345                 XsdElement elementValue = resolveElement(element);
346                 String variableName = Utils.toVariableName(getElementName(elementValue));
347                 out.printf("if (tagName.equals(\"%s\")) {\n", elementValue.getName());
348                 if (type instanceof JavaSimpleType) {
349                     out.print("raw = XmlParser.readText(parser);\n");
350                 }
351                 out.print(type.getParsingExpression());
352                 if (element.isMultiple()) {
353                     out.printf("instance.get%s().add(value);\n",
354                             Utils.capitalize(variableName));
355                 } else {
356                     out.printf("instance.set%s(value);\n",
357                             Utils.capitalize(variableName));
358                 }
359                 out.printf("} else ");
360             }
361             out.print("{\n"
362                     + "XmlParser.skip(parser);\n"
363                     + "}\n"
364                     + "}\n");
365             out.printf("if (type != org.xmlpull.v1.XmlPullParser.END_TAG) {\n"
366                     + "throw new javax.xml.datatype.DatatypeConfigurationException(\"%s is not closed\");\n"
367                     + "}\n", name);
368         } else {
369             out.print("XmlParser.skip(parser);\n");
370         }
371         out.print("return instance;\n"
372                 + "}\n");
373     }
374 
printWriter(CodeWriter out, String name, XsdComplexType complexType)375     private void printWriter(CodeWriter out, String name, XsdComplexType complexType)
376             throws JavaCodeGeneratorException {
377         JavaSimpleType baseValueType = (complexType instanceof XsdSimpleContent) ?
378                 getValueType((XsdSimpleContent) complexType, true) : null;
379         List<XsdElement> allElements = new ArrayList<>();
380         List<XsdAttribute> allAttributes = new ArrayList<>();
381         stackComponents(complexType, allElements, allAttributes);
382 
383         // parse types for elements and attributes
384         List<JavaType> allElementTypes = new ArrayList<>();
385         for (XsdElement element : allElements) {
386             XsdElement elementValue = resolveElement(element);
387             JavaType javaType = parseType(elementValue.getType(), elementValue.getName());
388             allElementTypes.add(javaType);
389         }
390         List<JavaSimpleType> allAttributeTypes = new ArrayList<>();
391         for (XsdAttribute attribute : allAttributes) {
392             XsdType type = resolveAttribute(attribute).getType();
393             allAttributeTypes.add(parseSimpleType(type, false));
394         }
395 
396         out.printf("\nvoid write(%sXmlWriter out, %sString name) " +
397                 "throws java.io.IOException {\n", getDefaultNullability(Nullability.NON_NULL),
398                 getDefaultNullability(Nullability.NON_NULL));
399 
400         out.print("out.print(\"<\" + name);\n");
401         for (int i = 0; i < allAttributes.size(); ++i) {
402             JavaType type = allAttributeTypes.get(i);
403             boolean isList = allAttributeTypes.get(i).isList();
404             XsdAttribute attribute = resolveAttribute(allAttributes.get(i));
405             String variableName = Utils.toVariableName(attribute.getName());
406             out.printf("if (has%s()) {\n", Utils.capitalize(variableName));
407             out.printf("out.print(\" %s=\\\"\");\n", attribute.getName());
408             out.print(type.getWritingExpression(String.format("%s%s()",
409                     getterName(type.getName()), Utils.capitalize(variableName)),
410                     attribute.getName()));
411             out.printf("out.print(\"\\\"\");\n}\n");
412         }
413         out.printf("out.print(\">\\n\");\n");
414 
415         if (!allElements.isEmpty()) {
416             out.printf("out.increaseIndent();\n");
417             for (int i = 0; i < allElements.size(); ++i) {
418                 JavaType type = allElementTypes.get(i);
419                 XsdElement element = allElements.get(i);
420                 XsdElement elementValue = resolveElement(element);
421                 String elementName = getElementName(elementValue);
422                 String variableName = Utils.toVariableName(elementName);
423 
424                 if (element.isMultiple()) {
425                     out.printf("for (%s value : get%s()) {\n", type.getName(),
426                             Utils.capitalize(variableName));
427                     if (type instanceof JavaSimpleType) {
428                         out.printf("out.print(\"<%s>\");\n", elementValue.getName());
429                     }
430                     out.print(type.getWritingExpression("value", elementValue.getName()));
431                     if (type instanceof JavaSimpleType) {
432                         out.printf("out.print(\"</%s>\\n\");\n", elementValue.getName());
433                     }
434                     out.print("}\n");
435                 } else {
436                     out.printf("if (has%s()) {\n", Utils.capitalize(variableName));
437                     if (type instanceof JavaSimpleType) {
438                         out.printf("out.print(\"<%s>\");\n", elementValue.getName());
439                     }
440                     out.print(type.getWritingExpression(String.format("%s%s()",
441                               getterName(type.getName()), Utils.capitalize(variableName)),
442                               elementValue.getName()));
443                     if (type instanceof JavaSimpleType) {
444                         out.printf("out.print(\"</%s>\\n\");\n", elementValue.getName());
445                     }
446                     out.printf("}\n");
447                 }
448 
449             }
450             out.printf("out.decreaseIndent();\n");
451         }
452         out.print("out.print(\"</\" + name + \">\\n\");\n");
453         out.print("}\n");
454     }
455 
printGetterAndSetter(CodeWriter out, JavaType type, String variableName, boolean isMultiple, XsdTag tag)456     private void printGetterAndSetter(CodeWriter out, JavaType type, String variableName,
457             boolean isMultiple, XsdTag tag) {
458         String typeName = isMultiple ? String.format("java.util.List<%s>", type.getNullableName())
459                 : type.getName();
460         boolean deprecated = tag == null ? false : tag.isDeprecated();
461         boolean finalValue = tag == null ? false : tag.isFinalValue();
462         Nullability nullability = tag == null ? Nullability.UNKNOWN : tag.getNullability();
463         out.println();
464         if (deprecated) {
465             out.printf("@java.lang.Deprecated\n");
466         }
467         out.printf("public%s %s%s %s%s() {\n", getFinalString(finalValue),
468                 getNullabilityString(nullability), typeName, getterName(typeName),
469                 Utils.capitalize(variableName));
470         if ((type instanceof JavaSimpleType && ((JavaSimpleType)type).isList()) || isMultiple) {
471             out.printf("if (%s == null) {\n"
472                     + "%s = new java.util.ArrayList<>();\n"
473                     + "}\n", variableName, variableName);
474         } else if (type.isPrimitiveType()) {
475             out.printf("if (%s == null) {\n", variableName);
476             if (typeName.equals("boolean")) {
477                 out.printf("return false;\n}\n", variableName);
478             } else {
479                 out.printf("return (%s)0;\n}\n", typeName);
480             }
481         }
482         out.printf("return %s;\n"
483                 + "}\n", variableName);
484 
485         if (isMultiple) return;
486         out.println();
487         out.printf("%sboolean has%s() {\n"
488                 + "if (%s == null) {\n"
489                 + "return false;\n"
490                 + "}\n"
491                 + "return true;\n}\n\n",
492                 generateHasMethod ? "public " : "",
493                 Utils.capitalize(variableName), variableName);
494         if (deprecated) {
495             out.printf("@java.lang.Deprecated\n");
496         }
497         out.printf("public%s void set%s(%s%s %s) {\n"
498                         + "this.%s = %s;\n"
499                         + "}\n",
500                 getFinalString(finalValue), Utils.capitalize(variableName),
501                 getNullabilityString(nullability), typeName, variableName,
502                 variableName, variableName);
503     }
504 
printXmlParser(CodeWriter out)505     private void printXmlParser(CodeWriter out) throws JavaCodeGeneratorException {
506         out.printf("package %s;\n", packageName);
507         out.println();
508         out.println("public class XmlParser {");
509 
510         boolean isMultiRootElement = xmlSchema.getElementMap().values().size() > 1;
511         for (XsdElement element : xmlSchema.getElementMap().values()) {
512             JavaType javaType = parseType(element.getType(), element.getName());
513             out.printf("public static %s%s read%s(%sjava.io.InputStream in)"
514                 + " throws org.xmlpull.v1.XmlPullParserException, java.io.IOException, "
515                 + "javax.xml.datatype.DatatypeConfigurationException {\n"
516                 + "org.xmlpull.v1.XmlPullParser parser = org.xmlpull.v1.XmlPullParserFactory"
517                 + ".newInstance().newPullParser();\n"
518                 + "parser.setFeature(org.xmlpull.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES, "
519                 + "true);\n"
520                 + "parser.setInput(in, null);\n"
521                 + "parser.nextTag();\n"
522                 + "String tagName = parser.getName();\n"
523                 + "String raw = null;\n", getDefaultNullability(Nullability.NULLABLE),
524                 javaType.getName(), isMultiRootElement ? Utils.capitalize(javaType.getName()) : "",
525                 getDefaultNullability(Nullability.NON_NULL));
526             out.printf("if (tagName.equals(\"%s\")) {\n", element.getName());
527             if (javaType instanceof JavaSimpleType) {
528                 out.print("raw = XmlParser.readText(parser);\n");
529             }
530             out.print(javaType.getParsingExpression());
531             out.print("return value;\n"
532                     + "}\n"
533                     + "return null;\n"
534                     + "}\n");
535             out.println();
536         }
537 
538         out.printf(
539                 "public static %sjava.lang.String readText(%sorg.xmlpull.v1.XmlPullParser parser)"
540                         + " throws org.xmlpull.v1.XmlPullParserException, java.io.IOException {\n"
541                         + "String result = \"\";\n"
542                         + "if (parser.next() == org.xmlpull.v1.XmlPullParser.TEXT) {\n"
543                         + "    result = parser.getText();\n"
544                         + "    parser.nextTag();\n"
545                         + "}\n"
546                         + "return result;\n"
547                         + "}\n", getDefaultNullability(Nullability.NULLABLE),
548                         getDefaultNullability(Nullability.NON_NULL));
549         out.println();
550 
551         out.printf(
552                 "public static void skip(%sorg.xmlpull.v1.XmlPullParser parser)"
553                         + " throws org.xmlpull.v1.XmlPullParserException, java.io.IOException {\n"
554                         + "if (parser.getEventType() != org.xmlpull.v1.XmlPullParser.START_TAG) {\n"
555                         + "    throw new IllegalStateException();\n"
556                         + "}\n"
557                         + "int depth = 1;\n"
558                         + "while (depth != 0) {\n"
559                         + "    switch (parser.next()) {\n"
560                         + "        case org.xmlpull.v1.XmlPullParser.END_TAG:\n"
561                         + "            depth--;\n"
562                         + "            break;\n"
563                         + "        case org.xmlpull.v1.XmlPullParser.START_TAG:\n"
564                         + "            depth++;\n"
565                         + "            break;\n"
566                         + "    }\n"
567                         + "}\n"
568                         + "}\n", getDefaultNullability(Nullability.NON_NULL));
569 
570         out.println("}");
571     }
572 
printXmlWriter(CodeWriter out)573     private void printXmlWriter(CodeWriter out) throws JavaCodeGeneratorException {
574         out.printf("package %s;\n", packageName);
575         out.println();
576         out.println("public class XmlWriter implements java.io.Closeable {");
577 
578         out.printf("private java.io.PrintWriter out;\n"
579                 + "private StringBuilder outBuffer;\n"
580                 + "private int indent;\n"
581                 + "private boolean startLine;\n\n"
582                 + "public XmlWriter(%sjava.io.PrintWriter printWriter) {\n"
583                 + "    out = printWriter;\n"
584                 + "    outBuffer = new StringBuilder();\n"
585                 + "    indent = 0;\n"
586                 + "    startLine = true;\n"
587                 + "}\n\n"
588                 + "private void printIndent() {\n"
589                 + "    assert startLine;\n"
590                 + "    for (int i = 0; i < indent; ++i) {\n"
591                 + "        outBuffer.append(\"    \");\n"
592                 + "    }\n"
593                 + "    startLine = false;\n"
594                 + "}\n\n"
595                 + "void print(String code) {\n"
596                 + "    String[] lines = code.split(\"\\n\", -1);\n"
597                 + "    for (int i = 0; i < lines.length; ++i) {\n"
598                 + "        if (startLine && !lines[i].isEmpty()) {\n"
599                 + "            printIndent();\n"
600                 + "        }\n"
601                 + "        outBuffer.append(lines[i]);\n"
602                 + "        if (i + 1 < lines.length) {\n"
603                 + "            outBuffer.append(\"\\n\");\n"
604                 + "            startLine = true;\n"
605                 + "        }\n"
606                 + "    }\n"
607                 + "}\n\n"
608                 + "void increaseIndent() {\n"
609                 + "    ++indent;\n}\n\n"
610                 + "void decreaseIndent() {\n"
611                 + "    --indent;\n"
612                 + "}\n\n"
613                 + "void printXml() {\n"
614                 + "    out.print(outBuffer.toString());\n"
615                 + "}\n\n"
616                 + "@Override\n"
617                 + "public void close() {\n"
618                 + "    if (out != null) {\n"
619                 + "        out.close();\n"
620                 + "    }\n"
621                 + "}\n\n", getDefaultNullability(Nullability.NON_NULL));
622 
623 
624         for (XsdElement element : xmlSchema.getElementMap().values()) {
625             JavaType javaType = parseType(element.getType(), element.getName());
626             String elementName = element.getName();
627             String VariableName = Utils.toVariableName(elementName);
628             String typeName = javaType instanceof JavaSimpleType ? javaType.getName() :
629                     Utils.toClassName(javaType.getName());
630             out.printf("public static void write(%sXmlWriter out, %s%s %s) "
631                     + "throws java.io.IOException {", getDefaultNullability(Nullability.NON_NULL),
632                     getDefaultNullability(Nullability.NON_NULL), typeName, VariableName);
633             out.print("\nout.print(\"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\\n\");\n");
634             out.printf("if (%s != null) {\n", VariableName);
635             out.printf("%s.write(out, \"%s\");\n}\n", VariableName, elementName);
636             out.print("out.printXml();\n}\n\n");
637         }
638         out.printf("}\n");
639     }
640 
printHexBinaryHelper(CodeWriter out)641     private void printHexBinaryHelper(CodeWriter out) throws JavaCodeGeneratorException {
642         out.printf("package %s;\n", packageName);
643         out.println();
644         out.println("public class HexBinaryHelper {");
645         out.print("public static byte[] hexStringToByteArray(String hexString) {\n"
646                 + "if (hexString.length() % 2 != 0) {\n"
647                 + "throw new IllegalArgumentException(\"length must be multiple of 2\");\n"
648                 + "}\n"
649                 + "byte[] outputBytes = new byte[hexString.length() / 2];\n"
650                 + "for (int i = 0; i < hexString.length(); i += 2) {\n"
651                 + "char c1 = hexString.charAt(i);\n"
652                 + "char c2 = hexString.charAt(i + 1);\n"
653                 + "outputBytes[i / 2] = (byte) ((Character.digit(c1, 16) << 4)"
654                 + " + Character.digit(c2, 16));\n"
655                 + "}\n"
656                 + "return outputBytes;"
657                 + "}\n\n"
658                 + "public static String byteArrayToHexString(byte[] b) {\n"
659                 + "StringBuffer s = new StringBuffer();\n"
660                 + "for (int i = 0; i < b.length; i++) {\n"
661                 + "s.append(Integer.toHexString(0x100 + (b[i] & 0xff)).substring(1));\n"
662                 + "}\n"
663                 + "return s.toString();\n"
664                 + "}\n"
665                 + "}\n");
666     }
667 
getElementName(XsdElement element)668     private String getElementName(XsdElement element) {
669         if (element instanceof XsdChoice) {
670             return element.getName() + "_optional";
671         } else if (element instanceof XsdAll) {
672             return element.getName() + "_all";
673         }
674         return element.getName();
675     }
676 
getFinalString(boolean finalValue)677     private String getFinalString(boolean finalValue) {
678         if (finalValue) {
679           return " final";
680         }
681         return "";
682     }
683 
getDefaultNullability(Nullability nullability)684     private String getDefaultNullability(Nullability nullability) {
685         if (showNullability) {
686             return getNullabilityString(nullability);
687         }
688         return "";
689     }
690 
getNullabilityString(Nullability nullability)691     private String getNullabilityString(Nullability nullability) {
692         if (nullability == Nullability.NON_NULL) {
693             return "@android.annotation.NonNull ";
694         } else if (nullability == Nullability.NULLABLE) {
695             return "@android.annotation.Nullable ";
696         } else if (showNullability) {
697             return "@android.annotation.Nullable ";
698         }
699         return "";
700     }
701 
getterName(String type)702     private String getterName(String type) {
703         if (type.equals("boolean") && booleanGetter) {
704             return "is";
705         }
706         return "get";
707     }
708 
stackComponents(XsdComplexType complexType, List<XsdElement> elements, List<XsdAttribute> attributes)709     private void stackComponents(XsdComplexType complexType, List<XsdElement> elements,
710             List<XsdAttribute> attributes) throws JavaCodeGeneratorException {
711         if (complexType.getBase() != null) {
712             QName baseRef = complexType.getBase().getRef();
713             if (baseRef != null && !baseRef.getNamespaceURI().equals(XsdConstants.XSD_NAMESPACE)) {
714                 XsdType parent = getType(baseRef.getLocalPart());
715                 if (parent instanceof XsdComplexType) {
716                     stackComponents((XsdComplexType) parent, elements, attributes);
717                 }
718             }
719         }
720         elements.addAll(getAllElements(complexType.getGroup()));
721         elements.addAll(complexType.getElements());
722         for (XsdAttributeGroup attributeGroup : complexType.getAttributeGroups()) {
723             attributes.addAll(getAllAttributes(resolveAttributeGroup(attributeGroup)));
724         }
725         attributes.addAll(complexType.getAttributes());
726     }
727 
getAllAttributes(XsdAttributeGroup attributeGroup)728     private List<XsdAttribute> getAllAttributes(XsdAttributeGroup attributeGroup)
729             throws JavaCodeGeneratorException {
730         List<XsdAttribute> attributes = new ArrayList<>();
731         for (XsdAttributeGroup attrGroup : attributeGroup.getAttributeGroups()) {
732             attributes.addAll(getAllAttributes(resolveAttributeGroup(attrGroup)));
733         }
734         attributes.addAll(attributeGroup.getAttributes());
735         return attributes;
736     }
737 
getAllElements(XsdGroup group)738     private List<XsdElement> getAllElements(XsdGroup group) throws JavaCodeGeneratorException {
739         List<XsdElement> elements = new ArrayList<>();
740         if (group == null) {
741             return elements;
742         }
743         elements.addAll(getAllElements(resolveGroup(group)));
744         elements.addAll(group.getElements());
745         return elements;
746     }
747 
getBaseName(XsdComplexType complexType)748     private String getBaseName(XsdComplexType complexType) throws JavaCodeGeneratorException {
749         if (complexType.getBase() == null) return null;
750         if (complexType.getBase().getRef().getNamespaceURI().equals(XsdConstants.XSD_NAMESPACE)) {
751             return null;
752         }
753         XsdType base = getType(complexType.getBase().getRef().getLocalPart());
754         if (base instanceof XsdComplexType) {
755             return Utils.toClassName(base.getName());
756         }
757         return null;
758     }
759 
getValueType(XsdSimpleContent simpleContent, boolean traverse)760     private JavaSimpleType getValueType(XsdSimpleContent simpleContent, boolean traverse)
761             throws JavaCodeGeneratorException {
762         assert simpleContent.getBase() != null;
763         QName baseRef = simpleContent.getBase().getRef();
764         assert baseRef != null;
765         if (baseRef.getNamespaceURI().equals(XsdConstants.XSD_NAMESPACE)) {
766             return predefinedType(baseRef.getLocalPart());
767         } else {
768             XsdType parent = getType(baseRef.getLocalPart());
769             if (parent instanceof XsdSimpleType) {
770                 return parseSimpleTypeReference(baseRef, false);
771             }
772             if (!traverse) return null;
773             if (parent instanceof XsdSimpleContent) {
774                 return getValueType((XsdSimpleContent) parent, true);
775             } else {
776                 throw new JavaCodeGeneratorException(
777                         String.format("base not simple : %s", baseRef.getLocalPart()));
778             }
779         }
780     }
781 
parseType(XsdType type, String defaultName)782     private JavaType parseType(XsdType type, String defaultName) throws JavaCodeGeneratorException {
783         if (type.getRef() != null) {
784             String name = type.getRef().getLocalPart();
785             if (type.getRef().getNamespaceURI().equals(XsdConstants.XSD_NAMESPACE)) {
786                 return predefinedType(name);
787             } else {
788                 XsdType typeValue = getType(name);
789                 if (typeValue instanceof XsdSimpleType) {
790                     return parseSimpleTypeReference(type.getRef(), false);
791                 }
792                 return parseType(typeValue, name);
793             }
794         }
795         if (type instanceof XsdComplexType) {
796             return new JavaComplexType(Utils.toClassName(defaultName));
797         } else if (type instanceof XsdSimpleType) {
798             return parseSimpleTypeValue((XsdSimpleType) type, false);
799         } else {
800             throw new JavaCodeGeneratorException(
801                     String.format("unknown type name : %s", defaultName));
802         }
803     }
804 
parseSimpleType(XsdType type, boolean traverse)805     private JavaSimpleType parseSimpleType(XsdType type, boolean traverse)
806             throws JavaCodeGeneratorException {
807         if (type.getRef() != null) {
808             return parseSimpleTypeReference(type.getRef(), traverse);
809         } else {
810             return parseSimpleTypeValue((XsdSimpleType) type, traverse);
811         }
812     }
813 
parseSimpleTypeReference(QName typeRef, boolean traverse)814     private JavaSimpleType parseSimpleTypeReference(QName typeRef, boolean traverse)
815             throws JavaCodeGeneratorException {
816         assert typeRef != null;
817         String typeName = typeRef.getLocalPart();
818         if (typeRef.getNamespaceURI().equals(XsdConstants.XSD_NAMESPACE)) {
819             return predefinedType(typeName);
820         }
821         if (javaSimpleTypeMap.containsKey(typeName)) {
822             return javaSimpleTypeMap.get(typeName);
823         } else if (traverse) {
824             XsdSimpleType simpleType = getSimpleType(typeName);
825             JavaSimpleType ret = parseSimpleTypeValue(simpleType, true);
826             javaSimpleTypeMap.put(typeName, ret);
827             return ret;
828         } else {
829             throw new JavaCodeGeneratorException(String.format("unknown type name : %s", typeName));
830         }
831     }
832 
parseSimpleTypeValue(XsdSimpleType simpleType, boolean traverse)833     private JavaSimpleType parseSimpleTypeValue(XsdSimpleType simpleType, boolean traverse)
834             throws JavaCodeGeneratorException {
835         if (simpleType instanceof XsdList) {
836             XsdList list = (XsdList) simpleType;
837             return parseSimpleType(list.getItemType(), traverse).newListType();
838         } else if (simpleType instanceof XsdRestriction) {
839             // we don't consider any restrictions.
840             XsdRestriction restriction = (XsdRestriction) simpleType;
841             if (restriction.getEnums() != null) {
842                 String name = Utils.toClassName(restriction.getName());
843                 return new JavaSimpleType(name, name, name + ".fromString(%s)", "%s.toString()",
844                         false);
845             }
846             return parseSimpleType(restriction.getBase(), traverse);
847         } else if (simpleType instanceof XsdUnion) {
848             // unions are almost always interpreted as java.lang.String
849             // Exceptionally, if any of member types of union are 'list', then we interpret it as
850             // List<String>
851             XsdUnion union = (XsdUnion) simpleType;
852             for (XsdType memberType : union.getMemberTypes()) {
853                 if (parseSimpleType(memberType, traverse).isList()) {
854                     return new JavaSimpleType("java.lang.String", "%s", true);
855                 }
856             }
857             return new JavaSimpleType("java.lang.String", "%s", false);
858         } else {
859             // unreachable
860             throw new IllegalStateException("unknown simple type");
861         }
862     }
863 
resolveElement(XsdElement element)864     private XsdElement resolveElement(XsdElement element) throws JavaCodeGeneratorException {
865         if (element.getRef() == null) return element;
866         String name = element.getRef().getLocalPart();
867         XsdElement ret = xmlSchema.getElementMap().get(name);
868         if (ret != null) return ret;
869         throw new JavaCodeGeneratorException(String.format("no element named : %s", name));
870     }
871 
resolveGroup(XsdGroup group)872     private XsdGroup resolveGroup(XsdGroup group) throws JavaCodeGeneratorException {
873         if (group.getRef() == null) return null;
874         String name = group.getRef().getLocalPart();
875         XsdGroup ret = xmlSchema.getGroupMap().get(name);
876         if (ret != null) return ret;
877         throw new JavaCodeGeneratorException(String.format("no group named : %s", name));
878     }
879 
resolveAttribute(XsdAttribute attribute)880     private XsdAttribute resolveAttribute(XsdAttribute attribute)
881             throws JavaCodeGeneratorException {
882         if (attribute.getRef() == null) return attribute;
883         String name = attribute.getRef().getLocalPart();
884         XsdAttribute ret = xmlSchema.getAttributeMap().get(name);
885         if (ret != null) return ret;
886         throw new JavaCodeGeneratorException(String.format("no attribute named : %s", name));
887     }
888 
resolveAttributeGroup(XsdAttributeGroup attributeGroup)889     private XsdAttributeGroup resolveAttributeGroup(XsdAttributeGroup attributeGroup)
890             throws JavaCodeGeneratorException {
891         if (attributeGroup.getRef() == null) return attributeGroup;
892         String name = attributeGroup.getRef().getLocalPart();
893         XsdAttributeGroup ret = xmlSchema.getAttributeGroupMap().get(name);
894         if (ret != null) return ret;
895         throw new JavaCodeGeneratorException(String.format("no attribute group named : %s", name));
896     }
897 
getType(String name)898     private XsdType getType(String name) throws JavaCodeGeneratorException {
899         XsdType type = xmlSchema.getTypeMap().get(name);
900         if (type != null) return type;
901         throw new JavaCodeGeneratorException(String.format("no type named : %s", name));
902     }
903 
getSimpleType(String name)904     private XsdSimpleType getSimpleType(String name) throws JavaCodeGeneratorException {
905         XsdType type = getType(name);
906         if (type instanceof XsdSimpleType) return (XsdSimpleType) type;
907         throw new JavaCodeGeneratorException(String.format("not a simple type : %s", name));
908     }
909 
predefinedType(String name)910     private JavaSimpleType predefinedType(String name) throws JavaCodeGeneratorException {
911         switch (name) {
912             case "string":
913             case "token":
914             case "normalizedString":
915             case "language":
916             case "ENTITY":
917             case "ID":
918             case "Name":
919             case "NCName":
920             case "NMTOKEN":
921             case "anyURI":
922             case "anyType":
923             case "QName":
924             case "NOTATION":
925             case "IDREF":
926                 return new JavaSimpleType("java.lang.String", "%s", false);
927             case "ENTITIES":
928             case "NMTOKENS":
929             case "IDREFS":
930                 return new JavaSimpleType("java.lang.String", "%s", true);
931             case "date":
932             case "dateTime":
933             case "time":
934             case "gDay":
935             case "gMonth":
936             case "gYear":
937             case "gMonthDay":
938             case "gYearMonth":
939                 return new JavaSimpleType("javax.xml.datatype.XMLGregorianCalendar",
940                         "javax.xml.datatype.XMLGregorianCalendar",
941                         "javax.xml.datatype.DatatypeFactory.newInstance()"
942                                 + ".newXMLGregorianCalendar(%s)",
943                         "%s.toString()", false);
944             case "duration":
945                 return new JavaSimpleType("javax.xml.datatype.Duration",
946                         "javax.xml.datatype.Duration",
947                         "javax.xml.datatype.DatatypeFactory.newInstance().newDuration(%s)",
948                         "%s.toString()", false);
949             case "decimal":
950                 return new JavaSimpleType("java.math.BigDecimal", "java.math.BigDecimal",
951                         "new java.math.BigDecimal(%s)", "%s.toString()", false);
952             case "integer":
953             case "negativeInteger":
954             case "nonNegativeInteger":
955             case "positiveInteger":
956             case "nonPositiveInteger":
957             case "unsignedLong":
958                 return new JavaSimpleType("java.math.BigInteger", "java.math.BigInteger",
959                         "new java.math.BigInteger(%s)", "%s.toString()", false);
960             case "long":
961             case "unsignedInt":
962                 return new JavaSimpleType("long", "java.lang.Long", "Long.parseLong(%s)",
963                         "Long.toString(%s)", false);
964             case "int":
965             case "unsignedShort":
966                 return new JavaSimpleType("int", "java.lang.Integer", "Integer.parseInt(%s)",
967                         "Integer.toString(%s)", false);
968             case "short":
969             case "unsignedByte":
970                 return new JavaSimpleType("short", "java.lang.Short", "Short.parseShort(%s)",
971                         "Short.toString(%s)", false);
972             case "byte":
973                 return new JavaSimpleType("byte", "java.lang.Byte", "Byte.parseByte(%s)",
974                         "Byte.toString(%s)",false);
975             case "boolean":
976                 return new JavaSimpleType("boolean", "java.lang.Boolean",
977                         "Boolean.parseBoolean(%s)", "Boolean.toString(%s)", false);
978             case "double":
979                 return new JavaSimpleType("double", "java.lang.Double", "Double.parseDouble(%s)",
980                         "Double.toString(%s)", false);
981             case "float":
982                 return new JavaSimpleType("float", "java.lang.Float", "Float.parseFloat(%s)",
983                         "Float.toString(%s)", false);
984             case "base64Binary":
985                 return new JavaSimpleType("byte[]", "byte[]",
986                         "java.util.Base64.getDecoder().decode(%s)",
987                         "java.util.Base64.getEncoder().encodeToString(%s)",
988                         false);
989             case "hexBinary":
990                 useHexBinary = true;
991                 return new JavaSimpleType("byte[]", "byte[]",
992                         "HexBinaryHelper.hexStringToByteArray(%s)",
993                         "HexBinaryHelper.byteArrayToHexString(%s)",
994                         false);
995         }
996         throw new JavaCodeGeneratorException("unknown xsd predefined type : " + name);
997     }
998 }
999