View Javadoc
1   package org.codehaus.modello.plugin.stax;
2   
3   /*
4    * Copyright (c) 2004, Codehaus.org
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy of
7    * this software and associated documentation files (the "Software"), to deal in
8    * the Software without restriction, including without limitation the rights to
9    * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10   * of the Software, and to permit persons to whom the Software is furnished to do
11   * so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  
25  import javax.inject.Named;
26  
27  import java.io.IOException;
28  import java.util.Arrays;
29  import java.util.List;
30  import java.util.Properties;
31  
32  import org.codehaus.modello.ModelloException;
33  import org.codehaus.modello.ModelloParameterConstants;
34  import org.codehaus.modello.model.Model;
35  import org.codehaus.modello.model.ModelAssociation;
36  import org.codehaus.modello.model.ModelClass;
37  import org.codehaus.modello.model.ModelDefault;
38  import org.codehaus.modello.model.ModelField;
39  import org.codehaus.modello.model.Version;
40  import org.codehaus.modello.model.VersionDefinition;
41  import org.codehaus.modello.plugin.java.javasource.JClass;
42  import org.codehaus.modello.plugin.java.javasource.JField;
43  import org.codehaus.modello.plugin.java.javasource.JMethod;
44  import org.codehaus.modello.plugin.java.javasource.JParameter;
45  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
46  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
47  import org.codehaus.modello.plugin.java.javasource.JType;
48  import org.codehaus.modello.plugin.java.metadata.JavaAssociationMetadata;
49  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
50  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
51  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
52  import org.codehaus.modello.plugins.xml.metadata.XmlModelMetadata;
53  import org.codehaus.plexus.util.StringUtils;
54  
55  /**
56   * @author <a href="mailto:jason@modello.org">Jason van Zyl</a>
57   * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
58   */
59  @Named("stax-reader")
60  public class StaxReaderGenerator extends AbstractStaxGenerator {
61  
62      private boolean requiresDomSupport;
63  
64      public void generate(Model model, Properties parameters) throws ModelloException {
65          initialize(model, parameters);
66  
67          requiresDomSupport = false;
68  
69          try {
70              generateStaxReader();
71  
72              VersionDefinition versionDefinition = model.getVersionDefinition();
73              if (versionDefinition != null) {
74                  String versions = parameters.getProperty(ModelloParameterConstants.ALL_VERSIONS);
75  
76                  if (versions != null) {
77                      generateStaxReaderDelegate(Arrays.asList(versions.split(",")));
78                  }
79              }
80          } catch (IOException ex) {
81              throw new ModelloException("Exception while generating StAX Reader.", ex);
82          }
83      }
84  
85      /**
86       * Generate a StAX reader, a <code><i>ModelName</i>StaxReader</code> class in <code>io.stax</code> sub-package
87       * with <code>public <i>RootClass</i> read( ... )</code> methods.
88       *
89       * @throws ModelloException
90       * @throws IOException
91       */
92      private void generateStaxReader() throws ModelloException, IOException {
93          Model objectModel = getModel();
94  
95          String packageName =
96                  objectModel.getDefaultPackageName(isPackageWithVersion(), getGeneratedVersion()) + ".io.stax";
97  
98          String unmarshallerName = getFileName("StaxReader");
99  
100         JSourceWriter sourceWriter = newJSourceWriter(packageName, unmarshallerName);
101 
102         JClass jClass = new JClass(packageName + '.' + unmarshallerName);
103         initHeader(jClass);
104         suppressAllWarnings(objectModel, jClass);
105 
106         jClass.addImport("java.io.IOException");
107         jClass.addImport("java.io.Reader");
108         jClass.addImport("java.io.FileInputStream");
109         jClass.addImport("java.io.InputStream");
110         jClass.addImport("java.io.StringWriter");
111         jClass.addImport("java.io.StringReader");
112         jClass.addImport("java.io.ByteArrayInputStream");
113         jClass.addImport("java.io.InputStreamReader");
114         jClass.addImport("java.text.DateFormat");
115         jClass.addImport("java.text.ParsePosition");
116         jClass.addImport("java.util.regex.Matcher");
117         jClass.addImport("java.util.regex.Pattern");
118         jClass.addImport("java.util.Locale");
119         jClass.addImport("javax.xml.XMLConstants");
120         jClass.addImport("javax.xml.stream.*");
121 
122         addModelImports(jClass, null);
123 
124         // ----------------------------------------------------------------------
125         // Write reference resolvers.
126         // ----------------------------------------------------------------------
127 
128         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
129         JClass rootType = new JClass(root.getName());
130 
131         GeneratorNode rootNode = findRequiredReferenceResolvers(root, null);
132 
133         writeReferenceResolvers(rootNode, jClass);
134         for (GeneratorNode node : rootNode.getNodesWithReferencableChildren().values()) {
135             writeReferenceResolvers(node, jClass);
136         }
137 
138         // ----------------------------------------------------------------------
139         // Write the class parsers
140         // ----------------------------------------------------------------------
141 
142         writeAllClassesParser(objectModel, jClass);
143 
144         // ----------------------------------------------------------------------
145         // Write helpers
146         // ----------------------------------------------------------------------
147 
148         writeHelpers(jClass);
149 
150         if (requiresDomSupport) {
151             writeBuildDomMethod(jClass);
152         }
153 
154         // ----------------------------------------------------------------------
155         // Write the read(XMLStreamReader,boolean) method which will do the unmarshalling.
156         // ----------------------------------------------------------------------
157 
158         JMethod unmarshall = new JMethod("read", rootType, null);
159         unmarshall.getModifiers().makePrivate();
160 
161         unmarshall.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
162         unmarshall.addParameter(new JParameter(JType.BOOLEAN, "strict"));
163 
164         unmarshall.addException(new JClass("IOException"));
165         unmarshall.addException(new JClass("XMLStreamException"));
166 
167         JSourceCode sc = unmarshall.getSourceCode();
168 
169         String tagName = resolveTagName(root);
170         String className = root.getName();
171         String variableName = uncapitalise(className);
172 
173         if (requiresDomSupport && !domAsXpp3) {
174             sc.add("if ( _doc_ == null )");
175             sc.add("{");
176             sc.indent();
177             sc.add("try");
178             sc.add("{");
179             sc.addIndented("initDoc();");
180             sc.add("}");
181             sc.add("catch ( javax.xml.parsers.ParserConfigurationException pce )");
182             sc.add("{");
183             sc.addIndented(
184                     "throw new XMLStreamException( \"Unable to create DOM document: \" + pce.getMessage(), pce );");
185             sc.add("}");
186             sc.unindent();
187             sc.add("}");
188         }
189 
190         sc.add("int eventType = xmlStreamReader.getEventType();");
191 
192         sc.add("String encoding = null;");
193 
194         sc.add("while ( eventType != XMLStreamConstants.END_DOCUMENT )");
195 
196         sc.add("{");
197         sc.indent();
198 
199         sc.add("if ( eventType == XMLStreamConstants.START_DOCUMENT )");
200         sc.add("{");
201         sc.addIndented("encoding = xmlStreamReader.getCharacterEncodingScheme();");
202         sc.add("}");
203 
204         sc.add("if ( eventType == XMLStreamConstants.START_ELEMENT )");
205 
206         sc.add("{");
207         sc.indent();
208 
209         sc.add("if ( strict && ! \"" + tagName + "\".equals( xmlStreamReader.getLocalName() ) )");
210 
211         sc.add("{");
212         sc.addIndented("throw new XMLStreamException( \"Expected root element '" + tagName + "' but "
213                 + "found '\" + xmlStreamReader.getLocalName() + \"'\", xmlStreamReader.getLocation(), null );");
214         sc.add("}");
215 
216         VersionDefinition versionDefinition = objectModel.getVersionDefinition();
217         if (versionDefinition != null && versionDefinition.isNamespaceType()) {
218             sc.add("String modelVersion = getVersionFromRootNamespace( xmlStreamReader );");
219 
220             writeModelVersionCheck(sc);
221         }
222 
223         sc.add(className + ' ' + variableName + " = parse" + root.getName() + "( xmlStreamReader, strict );");
224 
225         sc.add(variableName + ".setModelEncoding( encoding );");
226 
227         sc.add("resolveReferences( " + variableName + " );");
228 
229         sc.add("return " + variableName + ';');
230 
231         sc.unindent();
232         sc.add("}");
233 
234         sc.add("eventType = xmlStreamReader.next();");
235 
236         sc.unindent();
237         sc.add("}");
238 
239         sc.add("throw new XMLStreamException( \"Expected root element '" + tagName + "' but "
240                 + "found no element at all: invalid XML document\", xmlStreamReader.getLocation(), null );");
241 
242         jClass.addMethod(unmarshall);
243 
244         // ----------------------------------------------------------------------
245         // Write the read(Reader[,boolean]) methods which will do the unmarshalling.
246         // ----------------------------------------------------------------------
247 
248         unmarshall = new JMethod("read", rootType, null);
249 
250         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
251         unmarshall.addParameter(new JParameter(JType.BOOLEAN, "strict"));
252 
253         unmarshall.addException(new JClass("IOException"));
254         unmarshall.addException(new JClass("XMLStreamException"));
255 
256         sc = unmarshall.getSourceCode();
257 
258         sc.add("XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader( reader );");
259 
260         sc.add("");
261 
262         sc.add("return read( xmlStreamReader, strict );");
263 
264         jClass.addMethod(unmarshall);
265 
266         // ----------------------------------------------------------------------
267 
268         unmarshall = new JMethod("read", rootType, null);
269 
270         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
271 
272         unmarshall.addException(new JClass("IOException"));
273         unmarshall.addException(new JClass("XMLStreamException"));
274 
275         sc = unmarshall.getSourceCode();
276         sc.add("return read( reader, true );");
277 
278         jClass.addMethod(unmarshall);
279 
280         // ----------------------------------------------------------------------
281         // Write the read(InputStream[,boolean]) methods which will do the unmarshalling.
282         // ----------------------------------------------------------------------
283 
284         unmarshall = new JMethod("read", rootType, null);
285 
286         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "stream"));
287         unmarshall.addParameter(new JParameter(JType.BOOLEAN, "strict"));
288 
289         unmarshall.addException(new JClass("IOException"));
290         unmarshall.addException(new JClass("XMLStreamException"));
291 
292         sc = unmarshall.getSourceCode();
293 
294         sc.add("XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader( stream );");
295 
296         sc.add("");
297 
298         sc.add("return read( xmlStreamReader, strict );");
299 
300         jClass.addMethod(unmarshall);
301 
302         // ----------------------------------------------------------------------
303 
304         unmarshall = new JMethod("read", rootType, null);
305 
306         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "stream"));
307 
308         unmarshall.addException(new JClass("IOException"));
309         unmarshall.addException(new JClass("XMLStreamException"));
310 
311         sc = unmarshall.getSourceCode();
312         sc.add("return read( stream, true );");
313 
314         jClass.addMethod(unmarshall);
315 
316         // ----------------------------------------------------------------------
317         // Write the read(String[,boolean]) methods which will do the unmarshalling.
318         // ----------------------------------------------------------------------
319 
320         unmarshall = new JMethod("read", rootType, null);
321 
322         unmarshall.addParameter(new JParameter(new JClass("String"), "filePath"));
323 
324         unmarshall.addParameter(new JParameter(JType.BOOLEAN, "strict"));
325 
326         unmarshall.addException(new JClass("IOException"));
327         unmarshall.addException(new JClass("XMLStreamException"));
328 
329         sc = unmarshall.getSourceCode();
330 
331         sc.add("java.io.File file = new java.io.File( filePath );");
332 
333         sc.add("XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader( "
334                 + "file.toURI().toURL().toExternalForm(), new FileInputStream( file ) );");
335 
336         sc.add("");
337 
338         sc.add("return read( xmlStreamReader, strict );");
339 
340         jClass.addMethod(unmarshall);
341 
342         // ----------------------------------------------------------------------
343 
344         unmarshall = new JMethod("read", rootType, null);
345 
346         unmarshall.addParameter(new JParameter(new JClass("String"), "filePath"));
347 
348         unmarshall.addException(new JClass("IOException"));
349         unmarshall.addException(new JClass("XMLStreamException"));
350 
351         sc = unmarshall.getSourceCode();
352         sc.add("return read( filePath, true );");
353 
354         jClass.addMethod(unmarshall);
355 
356         // Determine the version. Currently, it causes the document to be reparsed, but could be made more efficient in
357         // future by buffering the read XML and piping that into any consequent read method.
358 
359         if (versionDefinition != null) {
360             writeDetermineVersionMethod(jClass, objectModel);
361         }
362 
363         // ----------------------------------------------------------------------
364         //
365         // ----------------------------------------------------------------------
366 
367         jClass.print(sourceWriter);
368 
369         sourceWriter.close();
370     }
371 
372     private void generateStaxReaderDelegate(List<String> versions) throws ModelloException, IOException {
373         Model objectModel = getModel();
374 
375         String packageName = objectModel.getDefaultPackageName(false, null) + ".io.stax";
376 
377         String unmarshallerName = getFileName("StaxReaderDelegate");
378 
379         JSourceWriter sourceWriter = newJSourceWriter(packageName, unmarshallerName);
380 
381         JClass jClass = new JClass(packageName + '.' + unmarshallerName);
382 
383         jClass.addImport("java.io.IOException");
384         jClass.addImport("java.io.Reader");
385 
386         jClass.addImport("javax.xml.stream.*");
387 
388         jClass.addImport("org.codehaus.plexus.util.IOUtil");
389         jClass.addImport("org.codehaus.plexus.util.xml.XmlStreamReader");
390 
391         JMethod method = new JMethod("read", new JClass("Object"), null);
392 
393         method.addParameter(new JParameter(new JClass("java.io.File"), "f"));
394 
395         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
396 
397         method.addException(new JClass("IOException"));
398         method.addException(new JClass("XMLStreamException"));
399 
400         jClass.addMethod(method);
401 
402         JSourceCode sc = method.getSourceCode();
403 
404         sc.add("String modelVersion;");
405         sc.add("Reader reader = new XmlStreamReader( f );");
406 
407         sc.add("try");
408         sc.add("{");
409         sc.addIndented("modelVersion = determineVersion( reader );");
410         sc.add("}");
411         sc.add("finally");
412         sc.add("{");
413         sc.addIndented("IOUtil.close( reader );");
414         sc.add("}");
415 
416         sc.add("reader = new XmlStreamReader( f );");
417         sc.add("try");
418         sc.add("{");
419         sc.indent();
420 
421         writeModelVersionHack(sc);
422 
423         String prefix = "";
424         for (String version : versions) {
425             sc.add(prefix + "if ( \"" + version + "\".equals( modelVersion ) )");
426             sc.add("{");
427             sc.addIndented("return new " + getModel().getDefaultPackageName(true, new Version(version)) + ".io.stax."
428                     + getFileName("StaxReader") + "().read( reader, strict );");
429             sc.add("}");
430 
431             prefix = "else ";
432         }
433 
434         sc.add("else");
435         sc.add("{");
436         sc.addIndented("throw new XMLStreamException( \"Document version '\" + modelVersion + \"' has no "
437                 + "corresponding reader.\" );");
438         sc.add("}");
439 
440         sc.unindent();
441         sc.add("}");
442         sc.add("finally");
443         sc.add("{");
444         sc.addIndented("IOUtil.close( reader );");
445         sc.add("}");
446 
447         // ----------------------------------------------------------------------
448 
449         method = new JMethod("read", new JClass("Object"), null);
450 
451         method.addParameter(new JParameter(new JClass("java.io.File"), "f"));
452 
453         method.addException(new JClass("IOException"));
454         method.addException(new JClass("XMLStreamException"));
455 
456         sc = method.getSourceCode();
457         sc.add("return read( f, true );");
458 
459         jClass.addMethod(method);
460 
461         writeDetermineVersionMethod(jClass, objectModel);
462 
463         jClass.print(sourceWriter);
464 
465         sourceWriter.close();
466     }
467 
468     private static void writeModelVersionHack(JSourceCode sc) {
469         sc.add("// legacy hack for pomVersion == 3");
470         sc.add("if ( \"3\".equals( modelVersion ) )");
471         sc.add("{");
472         sc.addIndented("modelVersion = \"3.0.0\";");
473         sc.add("}");
474     }
475 
476     private void writeDetermineVersionMethod(JClass jClass, Model objectModel) throws ModelloException {
477         VersionDefinition versionDefinition = objectModel.getVersionDefinition();
478 
479         JMethod method = new JMethod("determineVersion", new JClass("String"), null);
480 
481         method.addParameter(new JParameter(new JClass("Reader"), "reader"));
482 
483         method.addException(new JClass("IOException"));
484         method.addException(new JClass("XMLStreamException"));
485 
486         JSourceCode sc = method.getSourceCode();
487 
488         sc.add("XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader( reader );");
489 
490         sc.add("while ( xmlStreamReader.hasNext() )");
491 
492         sc.add("{");
493         sc.indent();
494 
495         sc.add("int eventType = xmlStreamReader.next();");
496 
497         sc.add("if ( eventType == XMLStreamConstants.START_ELEMENT )");
498 
499         sc.add("{");
500         sc.indent();
501 
502         if (versionDefinition.isNamespaceType()) {
503             XmlModelMetadata xmlModelMetadata = (XmlModelMetadata) objectModel.getMetadata(XmlModelMetadata.ID);
504 
505             String namespace = xmlModelMetadata.getNamespace();
506             if (namespace == null || !namespace.contains("${version}")) {
507                 throw new ModelloException("versionDefinition is namespace, but the model does not declare "
508                         + "xml.namespace on the model element");
509             }
510 
511             sc.add("return getVersionFromRootNamespace( xmlStreamReader );");
512 
513             writeNamespaceVersionGetMethod(namespace, jClass);
514         } else {
515             String value = versionDefinition.getValue();
516 
517             ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
518             ModelField field = root.getField(value, getGeneratedVersion());
519 
520             if (field == null) {
521                 throw new ModelloException("versionDefinition is field, but the model root element does not declare a "
522                         + "field '" + value + "'.");
523             }
524 
525             if (!"String".equals(field.getType())) {
526                 throw new ModelloException("versionDefinition is field, but the field is not of type String");
527             }
528 
529             sc.add("return getVersionFromField( xmlStreamReader );");
530 
531             writeFieldVersionGetMethod(field, jClass);
532         }
533 
534         sc.unindent();
535         sc.add("}");
536 
537         sc.unindent();
538         sc.add("}");
539 
540         sc.add("throw new XMLStreamException( \"Version not found in document\", xmlStreamReader.getLocation() );");
541 
542         jClass.addMethod(method);
543     }
544 
545     private static void writeFieldVersionGetMethod(ModelField field, JClass jClass) {
546         JMethod method = new JMethod("getVersionFromField", new JType("String"), null);
547         method.getModifiers().makePrivate();
548         method.addParameter(new JParameter(new JType("XMLStreamReader"), "xmlStreamReader"));
549         method.addException(new JClass("XMLStreamException"));
550         jClass.addMethod(method);
551 
552         JSourceCode sc = method.getSourceCode();
553 
554         XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
555         String value = xmlFieldMetadata.getTagName();
556         if (value == null) {
557             value = field.getName();
558         }
559 
560         // we are now at the root element. Search child elements for the correct tag name
561 
562         sc.add("int depth = 0;");
563 
564         sc.add("while ( depth >= 0 )");
565 
566         sc.add("{");
567         sc.indent();
568 
569         sc.add("int eventType = xmlStreamReader.next();");
570 
571         sc.add("if ( eventType == XMLStreamConstants.START_ELEMENT )");
572         sc.add("{");
573         sc.indent();
574 
575         sc.add("if ( depth == 0 && \"" + value + "\".equals( xmlStreamReader.getLocalName() ) )");
576         sc.add("{");
577         sc.addIndented("return xmlStreamReader.getElementText();");
578         sc.add("}");
579 
580         if (field.getAlias() != null) {
581             sc.add("if ( depth == 0 && \"" + field.getAlias() + "\".equals( xmlStreamReader.getLocalName() ) )");
582             sc.add("{");
583             sc.addIndented("return xmlStreamReader.getElementText();");
584             sc.add("}");
585         }
586 
587         sc.add("depth++;");
588 
589         sc.unindent();
590         sc.add("}");
591 
592         sc.add("if ( eventType == XMLStreamConstants.END_ELEMENT )");
593         sc.add("{");
594         sc.addIndented("depth--;");
595         sc.add("}");
596 
597         sc.unindent();
598         sc.add("}");
599 
600         sc.add("throw new XMLStreamException( \"Field: '" + value
601                 + "' does not exist in the document.\", xmlStreamReader.getLocation() );");
602     }
603 
604     private static void writeNamespaceVersionGetMethod(String namespace, JClass jClass) {
605         JMethod method = new JMethod("getVersionFromRootNamespace", new JType("String"), null);
606         method.getModifiers().makePrivate();
607         method.addParameter(new JParameter(new JType("XMLStreamReader"), "xmlStreamReader"));
608         method.addException(new JClass("XMLStreamException"));
609         jClass.addMethod(method);
610 
611         JSourceCode sc = method.getSourceCode();
612 
613         sc.add("String uri = xmlStreamReader.getNamespaceURI( \"\" );");
614 
615         sc.add("if ( uri == null )");
616 
617         sc.add("{");
618         sc.addIndented("throw new XMLStreamException( \"No namespace specified, but versionDefinition requires it\", "
619                 + "xmlStreamReader.getLocation() );");
620         sc.add("}");
621 
622         int index = namespace.indexOf("${version}");
623 
624         sc.add("String uriPrefix = \"" + namespace.substring(0, index) + "\";");
625         sc.add("String uriSuffix = \"" + namespace.substring(index + 10) + "\";");
626 
627         sc.add("if ( !uri.startsWith( uriPrefix ) || !uri.endsWith( uriSuffix ) )");
628 
629         sc.add("{");
630         sc.addIndented("throw new XMLStreamException( \"Namespace URI: '\" + uri + \"' does not match pattern '"
631                 + namespace + "'\", xmlStreamReader.getLocation() );");
632         sc.add("}");
633 
634         sc.add("return uri.substring( uriPrefix.length(), uri.length() - uriSuffix.length() );");
635     }
636 
637     /**
638      * Write code to parse every classes from a model.
639      *
640      * @param objectModel the model
641      * @param jClass the generated class source file
642      * @throws ModelloException
643      * @see {@link #writeClassParser(ModelClass, JClass, boolean)}
644      */
645     private void writeAllClassesParser(Model objectModel, JClass jClass) throws ModelloException {
646         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
647 
648         for (ModelClass clazz : getClasses(objectModel)) {
649             writeClassParser(clazz, jClass, root.getName().equals(clazz.getName()));
650         }
651     }
652 
653     /**
654      * Write a <code>private <i>ClassName</i> parse<i>ClassName</i>( ... )</code> method to parse a class from a model.
655      *
656      * @param modelClass the model class
657      * @param jClass the generated class source file
658      * @param rootElement is this class the root from the model?
659      * @throws ModelloException
660      */
661     private void writeClassParser(ModelClass modelClass, JClass jClass, boolean rootElement) throws ModelloException {
662         String className = modelClass.getName();
663 
664         String capClassName = capitalise(className);
665 
666         String uncapClassName = uncapitalise(className);
667 
668         JMethod unmarshall = new JMethod("parse" + capClassName, new JClass(className), null);
669         unmarshall.getModifiers().makePrivate();
670 
671         unmarshall.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
672         unmarshall.addParameter(new JParameter(JType.BOOLEAN, "strict"));
673 
674         unmarshall.addException(new JClass("IOException"));
675         unmarshall.addException(new JClass("XMLStreamException"));
676 
677         JSourceCode sc = unmarshall.getSourceCode();
678 
679         sc.add(className + ' ' + uncapClassName + " = new " + className + "();");
680 
681         ModelField contentField = getContentField(modelClass.getAllFields(getGeneratedVersion(), true));
682 
683         if (contentField != null) {
684             writeAttributes(modelClass, uncapClassName, sc);
685 
686             writePrimitiveField(
687                     contentField,
688                     contentField.getType(),
689                     uncapClassName,
690                     "set" + capitalise(contentField.getName()),
691                     sc);
692         } else {
693             sc.add("java.util.Set parsed = new java.util.HashSet();");
694 
695             String instanceFieldName = getInstanceFieldName(className);
696 
697             writeAttributes(modelClass, uncapClassName, sc);
698 
699             if (isAssociationPartToClass(modelClass)) {
700                 jClass.addField(new JField(new JType("java.util.Map"), instanceFieldName));
701 
702                 sc.add("if ( " + instanceFieldName + " == null )");
703                 sc.add("{");
704                 sc.addIndented(instanceFieldName + " = new java.util.HashMap();");
705                 sc.add("}");
706 
707                 sc.add("String v = xmlStreamReader.getAttributeValue( null, \"modello.id\" );");
708                 sc.add("if ( v != null )");
709                 sc.add("{");
710                 sc.addIndented(instanceFieldName + ".put( v, " + uncapClassName + " );");
711                 sc.add("}");
712             }
713 
714             sc.add(
715                     "while ( ( strict ? xmlStreamReader.nextTag() : nextTag( xmlStreamReader ) ) == XMLStreamConstants.START_ELEMENT )");
716 
717             sc.add("{");
718             sc.indent();
719 
720             boolean addElse = false;
721 
722             // Write other fields
723 
724             for (ModelField field : modelClass.getAllFields(getGeneratedVersion(), true)) {
725                 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
726 
727                 if (!xmlFieldMetadata.isAttribute() && !xmlFieldMetadata.isTransient()) {
728                     processField(field, xmlFieldMetadata, addElse, sc, uncapClassName, rootElement, jClass);
729 
730                     addElse = true;
731                 }
732             }
733 
734             /*
735                         if ( modelClass.getFields( getGeneratedVersion() ).size() > 0 )
736                         {
737                             sc.add( "else" );
738 
739                             sc.add( "{" );
740                             sc.addIndented( "parser.nextText();" );
741                             sc.add( "}" );
742                         }
743             */
744 
745             if (addElse) {
746                 sc.add("else");
747 
748                 sc.add("{");
749                 sc.indent();
750             }
751 
752             sc.add("checkUnknownElement( xmlStreamReader, strict );");
753 
754             if (addElse) {
755                 sc.unindent();
756                 sc.add("}");
757             }
758 
759             sc.unindent();
760             sc.add("}");
761 
762             // This must be last so that we guarantee the ID has been filled already
763             if (isAssociationPartToClass(modelClass)) {
764                 List<ModelField> identifierFields = modelClass.getIdentifierFields(getGeneratedVersion());
765 
766                 if (identifierFields.size() == 1) {
767                     ModelField field = (ModelField) identifierFields.get(0);
768 
769                     String v = uncapClassName + ".get" + capitalise(field.getName()) + "()";
770                     v = getValue(field.getType(), v, (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID));
771                     sc.add(instanceFieldName + ".put( " + v + ", " + uncapClassName + " );");
772                 }
773             }
774         }
775 
776         sc.add("return " + uncapClassName + ";");
777 
778         jClass.addMethod(unmarshall);
779     }
780 
781     private GeneratorNode findRequiredReferenceResolvers(ModelClass modelClass, GeneratorNode parent)
782             throws ModelloException {
783         String className = modelClass.getName();
784 
785         GeneratorNode value = new GeneratorNode(className, parent);
786 
787         for (ModelField field : modelClass.getAllFields(getGeneratedVersion(), true)) {
788             if (field instanceof ModelAssociation) {
789                 ModelAssociation association = (ModelAssociation) field;
790 
791                 if (isClassInModel(association.getTo(), getModel())) {
792                     ModelField referenceIdentifierField = getReferenceIdentifierField(association);
793 
794                     GeneratorNode child = null;
795                     if (referenceIdentifierField != null) {
796                         child = new GeneratorNode(association, parent);
797                         child.setReferencable(true);
798                     } else {
799                         if (!value.getChain().contains(association.getTo())) {
800                             // descend into child
801                             child = findRequiredReferenceResolvers(association.getToClass(), value);
802                             child.setAssociation(association);
803                         }
804                     }
805                     if (child != null) {
806                         value.addChild(child);
807                     }
808                 }
809             }
810         }
811 
812         // propagate the flag up
813         for (GeneratorNode child : value.getChildren()) {
814             if (child.isReferencable() || child.isReferencableChildren()) {
815                 value.setReferencableChildren(true);
816             }
817 
818             value.addNodesWithReferencableChildren(child.getNodesWithReferencableChildren());
819         }
820 
821         return value;
822     }
823 
824     private void writeReferenceResolvers(GeneratorNode node, JClass jClass) {
825         JMethod unmarshall = new JMethod("resolveReferences");
826 
827         unmarshall.addParameter(new JParameter(new JClass(node.getTo()), "value"));
828 
829         unmarshall.getModifiers().makePrivate();
830 
831         JSourceCode sc = unmarshall.getSourceCode();
832 
833         sc.add("java.util.Map refs;");
834 
835         for (GeneratorNode child : node.getChildren()) {
836             if (child.isReferencable()) {
837                 ModelAssociation association = child.getAssociation();
838                 String refFieldName = getRefFieldName(association);
839                 String to = association.getTo();
840                 String instanceFieldName = getInstanceFieldName(to);
841 
842                 sc.add("if ( " + refFieldName + " != null )");
843                 sc.add("{");
844                 sc.indent();
845 
846                 sc.add("refs = (java.util.Map) " + refFieldName + ".get( value );");
847 
848                 sc.add("if ( refs != null )");
849                 sc.add("{");
850                 sc.indent();
851 
852                 String capAssocName = capitalise(association.getName());
853                 if (association.isOneMultiplicity()) {
854                     sc.add("String id = (String) refs.get( \"" + association.getName() + "\" );");
855                     sc.add(to + " ref = (" + to + ") " + instanceFieldName + ".get( id );");
856 
857                     // Don't set if it already is, since the Java plugin generates create/break that will throw an
858                     // exception
859 
860                     sc.add("if ( ref != null && !ref.equals( value.get" + capAssocName + "() ) )");
861                     sc.add("{");
862                     sc.addIndented("value.set" + capAssocName + "( ref );");
863                     sc.add("}");
864                 } else {
865                     sc.add("for ( int i = 0; i < value.get" + capAssocName + "().size(); i++ )");
866                     sc.add("{");
867                     sc.indent();
868 
869                     sc.add("String id = (String) refs.get( \"" + association.getName() + ".\" + i );");
870                     sc.add(to + " ref = (" + to + ") " + instanceFieldName + ".get( id );");
871                     sc.add("if ( ref != null )");
872                     sc.add("{");
873                     sc.addIndented("value.get" + capAssocName + "().set( i, ref );");
874                     sc.add("}");
875 
876                     sc.unindent();
877                     sc.add("}");
878                 }
879 
880                 sc.unindent();
881                 sc.add("}");
882 
883                 sc.unindent();
884                 sc.add("}");
885             }
886 
887             if (child.isReferencableChildren()) {
888                 ModelAssociation association = child.getAssociation();
889                 if (association.isOneMultiplicity()) {
890                     sc.add("resolveReferences( value.get" + capitalise(association.getName()) + "() );");
891                 } else {
892                     sc.add("for ( java.util.Iterator i = value.get" + capitalise(association.getName())
893                             + "().iterator(); i.hasNext(); )");
894                     sc.add("{");
895                     sc.addIndented("resolveReferences( (" + association.getTo() + ") i.next() );");
896                     sc.add("}");
897                 }
898             }
899         }
900 
901         jClass.addMethod(unmarshall);
902     }
903 
904     private static String getRefFieldName(ModelAssociation association) {
905         return uncapitalise(association.getTo()) + "References";
906     }
907 
908     private static String getInstanceFieldName(String to) {
909         return uncapitalise(to) + "Instances";
910     }
911 
912     /**
913      * Add code to parse fields of a model class that are XML attributes.
914      *
915      * @param modelClass the model class
916      * @param uncapClassName
917      * @param sc the source code to add to
918      * @throws ModelloException
919      */
920     private void writeAttributes(ModelClass modelClass, String uncapClassName, JSourceCode sc) throws ModelloException {
921         for (ModelField field : modelClass.getAllFields(getGeneratedVersion(), true)) {
922             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
923 
924             if (xmlFieldMetadata.isAttribute() && !xmlFieldMetadata.isTransient()) {
925                 writePrimitiveField(field, field.getType(), uncapClassName, "set" + capitalise(field.getName()), sc);
926             }
927         }
928     }
929 
930     /**
931      * Generate code to process a field represented as an XML element.
932      *
933      * @param field the field to process
934      * @param xmlFieldMetadata its XML metadata
935      * @param addElse add an <code>else</code> statement before generating a new <code>if</code>
936      * @param sc the method source code to add to
937      * @param objectName the object name in the source
938      * @param rootElement is the enclosing model class the root class (for model version field handling)
939      * @param jClass the generated class source file
940      * @throws ModelloException
941      */
942     private void processField(
943             ModelField field,
944             XmlFieldMetadata xmlFieldMetadata,
945             boolean addElse,
946             JSourceCode sc,
947             String objectName,
948             boolean rootElement,
949             JClass jClass)
950             throws ModelloException {
951         String fieldTagName = resolveTagName(field, xmlFieldMetadata);
952 
953         String capFieldName = capitalise(field.getName());
954 
955         String singularName = singular(field.getName());
956 
957         String alias;
958         if (StringUtils.isEmpty(field.getAlias())) {
959             alias = "null";
960         } else {
961             alias = "\"" + field.getAlias() + "\"";
962         }
963 
964         String tagComparison = (addElse ? "else " : "") + "if ( checkFieldWithDuplicate( xmlStreamReader, \""
965                 + fieldTagName + "\", " + alias + ", parsed ) )";
966 
967         if (!(field instanceof ModelAssociation)) {
968             sc.add(tagComparison);
969 
970             sc.add("{");
971             sc.indent();
972 
973             // ModelField
974             writePrimitiveField(field, field.getType(), objectName, "set" + capFieldName, sc);
975 
976             if (rootElement && field.isModelVersionField()) {
977                 sc.add("String modelVersion = " + objectName + ".get" + capFieldName + "();");
978 
979                 writeModelVersionCheck(sc);
980             }
981 
982             sc.unindent();
983             sc.add("}");
984         } else { // model association
985             ModelAssociation association = (ModelAssociation) field;
986 
987             String associationName = association.getName();
988 
989             if (association.isOneMultiplicity()) {
990                 sc.add(tagComparison);
991 
992                 sc.add("{");
993                 sc.indent();
994 
995                 ModelField referenceIdentifierField = getReferenceIdentifierField(association);
996 
997                 if (referenceIdentifierField != null) {
998                     addCodeToAddReferences(association, jClass, sc, referenceIdentifierField, objectName);
999 
1000                     // gobble the rest of the tag
1001                     sc.add("while ( xmlStreamReader.getEventType() != XMLStreamConstants.END_ELEMENT )");
1002                     sc.add("{");
1003                     sc.addIndented("xmlStreamReader.next();");
1004                     sc.add("}");
1005                 } else {
1006                     sc.add(objectName + ".set" + capFieldName + "( parse" + association.getTo()
1007                             + "( xmlStreamReader, strict ) );");
1008                 }
1009 
1010                 sc.unindent();
1011                 sc.add("}");
1012             } else {
1013                 // MANY_MULTIPLICITY
1014 
1015                 XmlAssociationMetadata xmlAssociationMetadata =
1016                         (XmlAssociationMetadata) association.getAssociationMetadata(XmlAssociationMetadata.ID);
1017 
1018                 String valuesTagName = resolveTagName(fieldTagName, xmlAssociationMetadata);
1019 
1020                 String type = association.getType();
1021 
1022                 boolean wrappedItems = xmlAssociationMetadata.isWrappedItems();
1023 
1024                 if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
1025                     JavaFieldMetadata javaFieldMetadata =
1026                             (JavaFieldMetadata) association.getMetadata(JavaFieldMetadata.ID);
1027                     JavaAssociationMetadata javaAssociationMetadata =
1028                             (JavaAssociationMetadata) association.getAssociationMetadata(JavaAssociationMetadata.ID);
1029 
1030                     String adder;
1031 
1032                     if (wrappedItems) {
1033                         sc.add(tagComparison);
1034 
1035                         sc.add("{");
1036                         sc.indent();
1037 
1038                         if (javaFieldMetadata.isSetter()) {
1039                             sc.add(type + " " + associationName + " = " + association.getDefaultValue() + ";");
1040 
1041                             sc.add(objectName + ".set" + capFieldName + "( " + associationName + " );");
1042 
1043                             adder = associationName + ".add";
1044                         } else {
1045                             adder = objectName + ".add" + association.getTo();
1046                         }
1047 
1048                         sc.add("while ( xmlStreamReader.nextTag() == XMLStreamConstants.START_ELEMENT )");
1049 
1050                         sc.add("{");
1051                         sc.indent();
1052 
1053                         sc.add("if ( \"" + valuesTagName + "\".equals( xmlStreamReader.getLocalName() ) )");
1054 
1055                         sc.add("{");
1056                         sc.indent();
1057                     } else {
1058                         sc.add((addElse ? "else " : "") + "if ( \"" + valuesTagName
1059                                 + "\".equals( xmlStreamReader.getLocalName() ) )");
1060 
1061                         sc.add("{");
1062                         sc.indent();
1063 
1064                         if (javaFieldMetadata.isGetter() && javaFieldMetadata.isSetter()) {
1065                             sc.add(type + " " + associationName + " = " + objectName + ".get" + capFieldName + "();");
1066 
1067                             sc.add("if ( " + associationName + " == null )");
1068 
1069                             sc.add("{");
1070                             sc.indent();
1071 
1072                             sc.add(associationName + " = " + association.getDefaultValue() + ";");
1073 
1074                             sc.add(objectName + ".set" + capFieldName + "( " + associationName + " );");
1075 
1076                             sc.unindent();
1077                             sc.add("}");
1078 
1079                             adder = associationName + ".add";
1080                         } else {
1081                             adder = objectName + ".add" + association.getTo();
1082                         }
1083                     }
1084 
1085                     if (isClassInModel(
1086                             association.getTo(), field.getModelClass().getModel())) {
1087                         ModelField referenceIdentifierField = getReferenceIdentifierField(association);
1088 
1089                         if (referenceIdentifierField != null) {
1090                             addCodeToAddReferences(association, jClass, sc, referenceIdentifierField, objectName);
1091                         }
1092 
1093                         if (association.getTo().equals(field.getModelClass().getName())
1094                                 || !javaAssociationMetadata.isAdder()) {
1095                             // HACK: the addXXX method will cause an OOME when compiling a self-referencing class, so we
1096                             //  just add it to the array. This could disrupt the links if you are using break/create
1097                             //  constraints in modello.
1098                             // MODELLO-273 update: Use addXXX only if no other methods are available!
1099                             sc.add(adder + "( parse" + association.getTo() + "( xmlStreamReader, strict ) );");
1100                         } else {
1101                             sc.add(objectName + ".add" + capitalise(singular(associationName)) + "( parse"
1102                                     + association.getTo() + "( xmlStreamReader, strict ) );");
1103                         }
1104                     } else {
1105                         writePrimitiveField(association, association.getTo(), associationName, "add", sc);
1106                     }
1107 
1108                     if (wrappedItems) {
1109                         sc.unindent();
1110                         sc.add("}");
1111 
1112                         sc.add("else");
1113 
1114                         sc.add("{");
1115                         sc.addIndented("throw new XMLStreamException( \"Unrecognised tag: '\" + "
1116                                 + "xmlStreamReader.getLocalName() + \"'\", xmlStreamReader.getLocation() );");
1117                         sc.add("}");
1118 
1119                         sc.unindent();
1120                         sc.add("}");
1121 
1122                         sc.unindent();
1123                         sc.add("}");
1124                     } else {
1125 
1126                         sc.unindent();
1127                         sc.add("}");
1128                     }
1129                 } else {
1130                     // Map or Properties
1131 
1132                     sc.add(tagComparison);
1133 
1134                     sc.add("{");
1135                     sc.indent();
1136 
1137                     if (xmlAssociationMetadata.isMapExplode()) {
1138                         sc.add("while ( xmlStreamReader.nextTag() == XMLStreamConstants.START_ELEMENT )");
1139 
1140                         sc.add("{");
1141                         sc.indent();
1142 
1143                         sc.add("if ( \"" + valuesTagName + "\".equals( xmlStreamReader.getLocalName() ) )");
1144 
1145                         sc.add("{");
1146                         sc.indent();
1147 
1148                         sc.add("String key = null;");
1149 
1150                         sc.add("String value = null;");
1151 
1152                         sc.add("// " + xmlAssociationMetadata.getMapStyle() + " mode.");
1153 
1154                         sc.add("while ( xmlStreamReader.nextTag() == XMLStreamConstants.START_ELEMENT )");
1155 
1156                         sc.add("{");
1157                         sc.indent();
1158 
1159                         sc.add("if ( \"key\".equals( xmlStreamReader.getLocalName() ) )");
1160 
1161                         sc.add("{");
1162                         sc.addIndented("key = xmlStreamReader.getElementText();");
1163                         sc.add("}");
1164 
1165                         sc.add("else if ( \"value\".equals( xmlStreamReader.getLocalName() ) )");
1166 
1167                         sc.add("{");
1168                         sc.addIndented("value = xmlStreamReader.getElementText()"
1169                                 + (xmlFieldMetadata.isTrim() ? ".trim()" : "") + ";");
1170                         sc.add("}");
1171 
1172                         sc.add("else");
1173 
1174                         sc.add("{");
1175                         sc.addIndented("xmlStreamReader.getText();");
1176                         sc.add("}");
1177 
1178                         sc.unindent();
1179                         sc.add("}");
1180 
1181                         sc.add(objectName + ".add" + capitalise(singularName) + "( key, value );");
1182 
1183                         sc.unindent();
1184                         sc.add("}");
1185 
1186                         sc.add("xmlStreamReader.next();");
1187 
1188                         sc.unindent();
1189                         sc.add("}");
1190                     } else {
1191                         // INLINE Mode
1192 
1193                         sc.add("while ( xmlStreamReader.nextTag() == XMLStreamConstants.START_ELEMENT )");
1194 
1195                         sc.add("{");
1196                         sc.indent();
1197 
1198                         sc.add("String key = xmlStreamReader.getLocalName();");
1199 
1200                         sc.add("String value = xmlStreamReader.getElementText()"
1201                                 + (xmlFieldMetadata.isTrim() ? ".trim()" : "") + ";");
1202 
1203                         sc.add(objectName + ".add" + capitalise(singularName) + "( key, value );");
1204 
1205                         sc.unindent();
1206                         sc.add("}");
1207                     }
1208 
1209                     sc.unindent();
1210                     sc.add("}");
1211                 }
1212             }
1213         }
1214     }
1215 
1216     private static void addCodeToAddReferences(
1217             ModelAssociation association,
1218             JClass jClass,
1219             JSourceCode sc,
1220             ModelField referenceIdentifierField,
1221             String referredFromClass) {
1222         String refFieldName = getRefFieldName(association);
1223         if (jClass.getField(refFieldName) == null) {
1224             jClass.addField(new JField(new JType("java.util.Map"), refFieldName));
1225         }
1226 
1227         sc.add("String value = xmlStreamReader.getAttributeValue( null, \"" + referenceIdentifierField.getName()
1228                 + "\" );");
1229         sc.add("if ( value != null )");
1230         sc.add("{");
1231         sc.indent();
1232 
1233         sc.add("// This is a reference to an element elsewhere in the model");
1234         sc.add("if ( " + refFieldName + " == null )");
1235         sc.add("{");
1236         sc.addIndented(refFieldName + " = new java.util.HashMap();");
1237         sc.add("}");
1238 
1239         sc.add("java.util.Map refs = (java.util.Map) " + refFieldName + ".get( " + referredFromClass + " );");
1240         sc.add("if ( refs == null )");
1241         sc.add("{");
1242         sc.indent();
1243 
1244         sc.add("refs = new java.util.HashMap();");
1245         sc.add(refFieldName + ".put( " + referredFromClass + ", refs );");
1246 
1247         sc.unindent();
1248         sc.add("}");
1249 
1250         if (association.isOneMultiplicity()) {
1251             sc.add("refs.put( \"" + association.getName() + "\", value );");
1252         } else {
1253             sc.add("refs.put( \"" + association.getName() + ".\" + " + association.getName() + ".size(), value );");
1254         }
1255 
1256         sc.unindent();
1257         sc.add("}");
1258     }
1259 
1260     private void writeModelVersionCheck(JSourceCode sc) {
1261         writeModelVersionHack(sc);
1262 
1263         sc.add("if ( !\"" + getGeneratedVersion() + "\".equals( modelVersion ) )");
1264         sc.add("{");
1265         sc.addIndented(
1266                 "throw new XMLStreamException( \"Document model version of '\" + modelVersion + \"' doesn't match reader "
1267                         + "version of '" + getGeneratedVersion() + "'\", xmlStreamReader.getLocation() );");
1268         sc.add("}");
1269     }
1270 
1271     /**
1272      * Write code to set a primitive field with a value got from the parser, with appropriate default value, trimming
1273      * and required check logic.
1274      *
1275      * @param field the model field to set (either XML attribute or element)
1276      * @param type the type of the value read from XML
1277      * @param objectName the object name in source
1278      * @param setterName the setter method name
1279      * @param sc the source code to add to
1280      */
1281     private void writePrimitiveField(
1282             ModelField field, String type, String objectName, String setterName, JSourceCode sc) {
1283         XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
1284 
1285         String tagName = resolveTagName(field, xmlFieldMetadata);
1286 
1287         String parserGetter;
1288         if (xmlFieldMetadata.isAttribute()) {
1289             parserGetter = "xmlStreamReader.getAttributeValue( null, \"" + tagName + "\" )";
1290         } else {
1291             parserGetter = "xmlStreamReader.getElementText()";
1292         }
1293 
1294         /* TODO:
1295                 if ( xmlFieldMetadata.isRequired() )
1296                 {
1297                     parserGetter = "getRequiredAttributeValue( " + parserGetter + ", \"" + tagName + "\", parser, strict )";
1298                 }
1299         */
1300         if (field.getDefaultValue() != null) {
1301             parserGetter = "getDefaultValue( " + parserGetter + ", \"" + field.getDefaultValue() + "\" )";
1302         }
1303 
1304         if (xmlFieldMetadata.isTrim()) {
1305             parserGetter = "getTrimmedValue( " + parserGetter + " )";
1306         }
1307 
1308         if ("boolean".equals(type) || "Boolean".equals(type)) {
1309             sc.add(objectName + "." + setterName + "( getBooleanValue( " + parserGetter + ", \"" + tagName
1310                     + "\", xmlStreamReader ) );");
1311         } else if ("char".equals(type)) {
1312             sc.add(objectName + "." + setterName + "( getCharacterValue( " + parserGetter + ", \"" + tagName
1313                     + "\", xmlStreamReader ) );");
1314         } else if ("double".equals(type)) {
1315             sc.add(objectName + "." + setterName + "( getDoubleValue( " + parserGetter + ", \"" + tagName
1316                     + "\", xmlStreamReader, strict ) );");
1317         } else if ("float".equals(type)) {
1318             sc.add(objectName + "." + setterName + "( getFloatValue( " + parserGetter + ", \"" + tagName
1319                     + "\", xmlStreamReader, strict ) );");
1320         } else if ("int".equals(type)) {
1321             sc.add(objectName + "." + setterName + "( getIntegerValue( " + parserGetter + ", \"" + tagName
1322                     + "\", xmlStreamReader, strict ) );");
1323         } else if ("long".equals(type)) {
1324             sc.add(objectName + "." + setterName + "( getLongValue( " + parserGetter + ", \"" + tagName
1325                     + "\", xmlStreamReader, strict ) );");
1326         } else if ("short".equals(type)) {
1327             sc.add(objectName + "." + setterName + "( getShortValue( " + parserGetter + ", \"" + tagName
1328                     + "\", xmlStreamReader, strict ) );");
1329         } else if ("byte".equals(type)) {
1330             sc.add(objectName + "." + setterName + "( getByteValue( " + parserGetter + ", \"" + tagName
1331                     + "\", xmlStreamReader, strict ) );");
1332         } else if ("String".equals(type)) {
1333             // TODO: other Primitive types
1334             sc.add(objectName + "." + setterName + "( " + parserGetter + " );");
1335         } else if ("Date".equals(type)) {
1336             sc.add("String dateFormat = "
1337                     + (xmlFieldMetadata.getFormat() != null ? "\"" + xmlFieldMetadata.getFormat() + "\"" : "null")
1338                     + ";");
1339             sc.add(objectName + "." + setterName + "( getDateValue( " + parserGetter + ", \"" + tagName
1340                     + "\", dateFormat, xmlStreamReader ) );");
1341         } else if ("DOM".equals(type)) {
1342             sc.add(objectName + "." + setterName + "( buildDom( xmlStreamReader, " + xmlFieldMetadata.isTrim()
1343                     + " ) );");
1344 
1345             requiresDomSupport = true;
1346         } else {
1347             throw new IllegalArgumentException("Unknown type: " + type);
1348         }
1349     }
1350 
1351     private void writeBuildDomMethod(JClass jClass) {
1352         if (domAsXpp3) {
1353             jClass.addImport("org.codehaus.plexus.util.xml.Xpp3Dom");
1354         } else {
1355             jClass.addField(new JField(new JClass("org.w3c.dom.Document"), "_doc_"));
1356             JMethod method = new JMethod("initDoc", null, null);
1357             method.getModifiers().makePrivate();
1358             method.addException(new JClass("javax.xml.parsers.ParserConfigurationException"));
1359 
1360             JSourceCode sc = method.getSourceCode();
1361             sc.add(
1362                     "javax.xml.parsers.DocumentBuilderFactory dbfac = javax.xml.parsers.DocumentBuilderFactory.newInstance();");
1363             sc.add("javax.xml.parsers.DocumentBuilder docBuilder = dbfac.newDocumentBuilder();");
1364             sc.add("_doc_ = docBuilder.newDocument();");
1365             jClass.addMethod(method);
1366         }
1367         String type = domAsXpp3 ? "Xpp3Dom" : "org.w3c.dom.Element";
1368         JMethod method = new JMethod("buildDom", new JType(type), null);
1369         method.getModifiers().makePrivate();
1370         method.addParameter(new JParameter(new JType("XMLStreamReader"), "xmlStreamReader"));
1371         method.addParameter(new JParameter(JType.BOOLEAN, "trim"));
1372         method.addException(new JClass("XMLStreamException"));
1373 
1374         JSourceCode sc = method.getSourceCode();
1375 
1376         sc.add("java.util.Stack elements = new java.util.Stack();");
1377 
1378         sc.add("java.util.Stack values = new java.util.Stack();");
1379 
1380         sc.add("int eventType = xmlStreamReader.getEventType();");
1381 
1382         sc.add("boolean spacePreserve = false;");
1383 
1384         sc.add("while ( xmlStreamReader.hasNext() )");
1385         sc.add("{");
1386         sc.indent();
1387 
1388         sc.add("if ( eventType == XMLStreamConstants.START_ELEMENT )");
1389         sc.add("{");
1390         sc.indent();
1391         sc.add("spacePreserve = false;");
1392         sc.add("String rawName = xmlStreamReader.getLocalName();");
1393 
1394         if (domAsXpp3) {
1395             sc.add("Xpp3Dom element = new Xpp3Dom( rawName );");
1396         } else {
1397             sc.add("org.w3c.dom.Element element = _doc_.createElement( rawName );");
1398         }
1399 
1400         sc.add("if ( !elements.empty() )");
1401         sc.add("{");
1402         sc.indent();
1403         sc.add(type + " parent = (" + type + ") elements.peek();");
1404 
1405         sc.add("parent." + (domAsXpp3 ? "addChild" : "appendChild") + "( element );");
1406         sc.unindent();
1407         sc.add("}");
1408 
1409         sc.add("elements.push( element );");
1410 
1411         sc.add("if ( xmlStreamReader.isEndElement() )");
1412         sc.add("{");
1413         sc.addIndented("values.push( null );");
1414         sc.add("}");
1415         sc.add("else");
1416         sc.add("{");
1417         sc.addIndented("values.push( new StringBuilder() );");
1418         sc.add("}");
1419 
1420         sc.add("int attributesSize = xmlStreamReader.getAttributeCount();");
1421 
1422         sc.add("for ( int i = 0; i < attributesSize; i++ )");
1423         sc.add("{");
1424         sc.indent();
1425         sc.add("String prefix = xmlStreamReader.getAttributePrefix( i );");
1426         sc.add("String localName = xmlStreamReader.getAttributeLocalName( i );");
1427         sc.add("String value = xmlStreamReader.getAttributeValue( i );");
1428         sc.add("if ( XMLConstants.DEFAULT_NS_PREFIX.equals( xmlStreamReader.getAttributeName( i ).getPrefix() ) )");
1429         sc.add("{");
1430         sc.addIndented("element.setAttribute( localName, value );");
1431         sc.add("}");
1432         sc.add("else");
1433         sc.add("{");
1434         sc.addIndented("element.setAttribute( prefix + ':'+ localName , value );");
1435         sc.add("}");
1436         sc.add(
1437                 "spacePreserve = spacePreserve || ( \"xml\".equals( prefix ) && \"space\".equals( localName ) && \"preserve\".equals( value ) );");
1438         sc.unindent();
1439         sc.add("}");
1440         sc.unindent();
1441         sc.add("}");
1442         sc.add("else if ( eventType == XMLStreamConstants.CHARACTERS )");
1443         sc.add("{");
1444         sc.indent();
1445         sc.add("StringBuilder valueBuffer = (StringBuilder) values.peek();");
1446 
1447         sc.add("String text = xmlStreamReader.getText();");
1448 
1449         sc.add("if ( trim && !spacePreserve )");
1450         sc.add("{");
1451         sc.addIndented("text = text.trim();");
1452         sc.add("}");
1453 
1454         sc.add("valueBuffer.append( text );");
1455         sc.unindent();
1456         sc.add("}");
1457         sc.add("else if ( eventType == XMLStreamConstants.END_ELEMENT )");
1458         sc.add("{");
1459         sc.indent();
1460 
1461         sc.add(type + " element = (" + type + ") elements.pop();");
1462 
1463         sc.add("// this Object could be null if it is a singleton tag");
1464         sc.add("Object accumulatedValue = values.pop();");
1465 
1466         sc.add("if ( " + (domAsXpp3 ? "element.getChildCount() == 0" : "!element.hasChildNodes()") + " )");
1467         sc.add("{");
1468         sc.addIndented("element." + (domAsXpp3 ? "setValue" : "setTextContent")
1469                 + "( ( accumulatedValue == null ) ? null : accumulatedValue.toString() );");
1470         sc.add("}");
1471 
1472         sc.add("if ( values.empty() )");
1473         sc.add("{");
1474         sc.addIndented("return element;");
1475         sc.add("}");
1476         sc.unindent();
1477         sc.add("}");
1478 
1479         sc.add("eventType = xmlStreamReader.next();");
1480 
1481         sc.unindent();
1482         sc.add("}");
1483 
1484         sc.add("throw new IllegalStateException( \"End of document found before returning to 0 depth\" );");
1485 
1486         jClass.addMethod(method);
1487     }
1488 
1489     private void writeHelpers(JClass jClass) {
1490         JMethod method = new JMethod("getTrimmedValue", new JClass("String"), null);
1491         method.getModifiers().makePrivate();
1492 
1493         method.addParameter(new JParameter(new JClass("String"), "s"));
1494 
1495         JSourceCode sc = method.getSourceCode();
1496 
1497         sc.add("if ( s != null )");
1498 
1499         sc.add("{");
1500         sc.addIndented("s = s.trim();");
1501         sc.add("}");
1502 
1503         sc.add("return s;");
1504 
1505         jClass.addMethod(method);
1506 
1507         // --------------------------------------------------------------------
1508 
1509         method = new JMethod("getDefaultValue", new JClass("String"), null);
1510         method.getModifiers().makePrivate();
1511 
1512         method.addParameter(new JParameter(new JClass("String"), "s"));
1513         method.addParameter(new JParameter(new JClass("String"), "v"));
1514 
1515         sc = method.getSourceCode();
1516 
1517         sc.add("if ( s == null )");
1518 
1519         sc.add("{");
1520         sc.addIndented("s = v;");
1521         sc.add("}");
1522 
1523         sc.add("return s;");
1524 
1525         jClass.addMethod(method);
1526 
1527         // --------------------------------------------------------------------
1528 
1529         method = new JMethod("getRequiredAttributeValue", new JClass("String"), null);
1530         method.addException(new JClass("XMLStreamException"));
1531         method.getModifiers().makePrivate();
1532 
1533         method.addParameter(new JParameter(new JClass("String"), "s"));
1534         method.addParameter(new JParameter(new JClass("String"), "attribute"));
1535         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1536         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
1537 
1538         sc = method.getSourceCode();
1539 
1540         sc.add("if ( s == null )");
1541 
1542         sc.add("{");
1543         sc.indent();
1544 
1545         sc.add("if ( strict )");
1546 
1547         sc.add("{");
1548         sc.addIndented("throw new XMLStreamException( \"Missing required value for attribute '\" + attribute + \"'\", "
1549                 + "xmlStreamReader.getLocation() );");
1550         sc.add("}");
1551 
1552         sc.unindent();
1553         sc.add("}");
1554 
1555         sc.add("return s;");
1556 
1557         jClass.addMethod(method);
1558 
1559         // --------------------------------------------------------------------
1560 
1561         method = new JMethod("getBooleanValue", JType.BOOLEAN, null);
1562         method.addException(new JClass("XMLStreamException"));
1563         method.getModifiers().makePrivate();
1564 
1565         method.addParameter(new JParameter(new JClass("String"), "s"));
1566         method.addParameter(new JParameter(new JClass("String"), "attribute"));
1567         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1568 
1569         sc = method.getSourceCode();
1570 
1571         sc.add("if ( s != null )");
1572 
1573         sc.add("{");
1574         sc.addIndented("return Boolean.valueOf( s ).booleanValue();");
1575         sc.add("}");
1576 
1577         sc.add("return false;");
1578 
1579         jClass.addMethod(method);
1580 
1581         // --------------------------------------------------------------------
1582 
1583         method = new JMethod("getCharacterValue", JType.CHAR, null);
1584         method.addException(new JClass("XMLStreamException"));
1585         method.getModifiers().makePrivate();
1586 
1587         method.addParameter(new JParameter(new JClass("String"), "s"));
1588         method.addParameter(new JParameter(new JClass("String"), "attribute"));
1589         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1590 
1591         sc = method.getSourceCode();
1592 
1593         sc.add("if ( s != null )");
1594 
1595         sc.add("{");
1596         sc.addIndented("return s.charAt( 0 );");
1597         sc.add("}");
1598 
1599         sc.add("return 0;");
1600 
1601         jClass.addMethod(method);
1602 
1603         // --------------------------------------------------------------------
1604 
1605         method = convertNumericalType("getIntegerValue", JType.INT, "Integer.valueOf( s ).intValue()", "an integer");
1606 
1607         jClass.addMethod(method);
1608 
1609         // --------------------------------------------------------------------
1610 
1611         method = convertNumericalType(
1612                 "getShortValue", JType.SHORT, "Short.valueOf( s ).shortValue()", "a short integer");
1613 
1614         jClass.addMethod(method);
1615 
1616         // --------------------------------------------------------------------
1617 
1618         method = convertNumericalType("getByteValue", JType.BYTE, "Byte.valueOf( s ).byteValue()", "a byte");
1619 
1620         jClass.addMethod(method);
1621 
1622         // --------------------------------------------------------------------
1623 
1624         method = convertNumericalType("getLongValue", JType.LONG, "Long.valueOf( s ).longValue()", "a long integer");
1625 
1626         jClass.addMethod(method);
1627 
1628         // --------------------------------------------------------------------
1629 
1630         method = convertNumericalType(
1631                 "getFloatValue", JType.FLOAT, "Float.valueOf( s ).floatValue()", "a floating point number");
1632 
1633         jClass.addMethod(method);
1634 
1635         // --------------------------------------------------------------------
1636 
1637         method = convertNumericalType(
1638                 "getDoubleValue", JType.DOUBLE, "Double.valueOf( s ).doubleValue()", "a floating point number");
1639 
1640         jClass.addMethod(method);
1641 
1642         // --------------------------------------------------------------------
1643 
1644         method = new JMethod("getDateValue", new JClass("java.util.Date"), null);
1645         method.addException(new JClass("XMLStreamException"));
1646         method.getModifiers().makePrivate();
1647 
1648         method.addParameter(new JParameter(new JClass("String"), "s"));
1649         method.addParameter(new JParameter(new JClass("String"), "attribute"));
1650         method.addParameter(new JParameter(new JClass("String"), "dateFormat"));
1651         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1652         method.addException(new JClass("XMLStreamException"));
1653 
1654         writeDateParsingHelper(
1655                 method.getSourceCode(), "new XMLStreamException( e.getMessage(), xmlStreamReader.getLocation(), e )");
1656 
1657         jClass.addMethod(method);
1658 
1659         // --------------------------------------------------------------------
1660 
1661         method = new JMethod("checkFieldWithDuplicate", JType.BOOLEAN, null);
1662         method.getModifiers().makePrivate();
1663 
1664         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1665         method.addParameter(new JParameter(new JClass("String"), "tagName"));
1666         method.addParameter(new JParameter(new JClass("String"), "alias"));
1667         method.addParameter(new JParameter(new JClass("java.util.Set"), "parsed"));
1668         method.addException(new JClass("XMLStreamException"));
1669 
1670         sc = method.getSourceCode();
1671 
1672         sc.add("if ( !( xmlStreamReader.getLocalName().equals( tagName ) ||"
1673                 + " xmlStreamReader.getLocalName().equals( alias ) ) )");
1674 
1675         sc.add("{");
1676         sc.addIndented("return false;");
1677         sc.add("}");
1678 
1679         sc.add("if ( !parsed.add( tagName ) )");
1680 
1681         sc.add("{");
1682         sc.addIndented(
1683                 "throw new XMLStreamException( \"Duplicated tag: '\" + tagName + \"'\", xmlStreamReader.getLocation() );");
1684         sc.add("}");
1685 
1686         sc.add("return true;");
1687 
1688         jClass.addMethod(method);
1689 
1690         // --------------------------------------------------------------------
1691 
1692         method = new JMethod("checkUnknownElement", null, null);
1693         method.getModifiers().makePrivate();
1694 
1695         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1696         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
1697         method.addException(new JClass("XMLStreamException"));
1698 
1699         sc = method.getSourceCode();
1700 
1701         sc.add("if ( strict )");
1702 
1703         sc.add("{");
1704         sc.addIndented("throw new XMLStreamException( \"Unrecognised tag: '\" + xmlStreamReader.getLocalName() + "
1705                 + "\"'\", xmlStreamReader.getLocation() );");
1706         sc.add("}");
1707 
1708         sc.add("int unrecognizedTagCount = 1;");
1709         sc.add("while( unrecognizedTagCount != 0 )");
1710 
1711         sc.add("{");
1712         sc.indent();
1713 
1714         sc.add("xmlStreamReader.next();");
1715         sc.add("if ( xmlStreamReader.getEventType() == XMLStreamConstants.START_ELEMENT )");
1716 
1717         sc.add("{");
1718         sc.addIndented("unrecognizedTagCount++;");
1719         sc.add("}");
1720 
1721         sc.add("else if ( xmlStreamReader.getEventType() == XMLStreamConstants.END_ELEMENT )");
1722         sc.add("{");
1723         sc.addIndented("unrecognizedTagCount--;");
1724         sc.add("}");
1725 
1726         sc.unindent();
1727         sc.add("}");
1728 
1729         jClass.addMethod(method);
1730 
1731         // --------------------------------------------------------------------
1732 
1733         method = new JMethod("nextTag", JType.INT, null);
1734         method.getModifiers().makePrivate();
1735 
1736         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1737         method.addException(new JClass("XMLStreamException"));
1738 
1739         sc = method.getSourceCode();
1740 
1741         sc.add("while ( true )");
1742         sc.add("{");
1743         sc.indent();
1744         sc.add("int eventType = xmlStreamReader.next();");
1745         sc.add("switch ( eventType )");
1746         sc.add("{");
1747         sc.indent();
1748         sc.add("case XMLStreamConstants.CHARACTERS:");
1749         sc.add("case XMLStreamConstants.CDATA:");
1750         sc.add("case XMLStreamConstants.SPACE:");
1751         sc.add("case XMLStreamConstants.PROCESSING_INSTRUCTION:");
1752         sc.add("case XMLStreamConstants.COMMENT:");
1753         sc.addIndented("break;");
1754         sc.add("case XMLStreamConstants.START_ELEMENT:");
1755         sc.add("case XMLStreamConstants.END_ELEMENT:");
1756         sc.addIndented("return eventType;");
1757         sc.add("default:");
1758         sc.addIndented("throw new XMLStreamException( \"expected start or end tag\", xmlStreamReader.getLocation() );");
1759         sc.unindent();
1760         sc.add("}");
1761         sc.unindent();
1762         sc.add("}");
1763 
1764         jClass.addMethod(method);
1765     }
1766 
1767     private JMethod convertNumericalType(String methodName, JType returnType, String expression, String typeDesc) {
1768         JMethod method = new JMethod(methodName, returnType, null);
1769         method.addException(new JClass("XMLStreamException"));
1770         method.getModifiers().makePrivate();
1771 
1772         method.addParameter(new JParameter(new JClass("String"), "s"));
1773         method.addParameter(new JParameter(new JClass("String"), "attribute"));
1774         method.addParameter(new JParameter(new JClass("XMLStreamReader"), "xmlStreamReader"));
1775         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
1776 
1777         JSourceCode sc = method.getSourceCode();
1778 
1779         sc.add("if ( s != null )");
1780 
1781         sc.add("{");
1782         sc.indent();
1783 
1784         sc.add("try");
1785 
1786         sc.add("{");
1787         sc.addIndented("return " + expression + ";");
1788         sc.add("}");
1789 
1790         sc.add("catch ( NumberFormatException nfe )");
1791 
1792         sc.add("{");
1793         sc.indent();
1794 
1795         sc.add("if ( strict )");
1796 
1797         sc.add("{");
1798         sc.addIndented("throw new XMLStreamException( \"Unable to parse element '\" + attribute + \"', must be "
1799                 + typeDesc + " but was '\" + s + \"'\", xmlStreamReader.getLocation(), nfe );");
1800         sc.add("}");
1801 
1802         sc.unindent();
1803         sc.add("}");
1804 
1805         sc.unindent();
1806         sc.add("}");
1807 
1808         sc.add("return 0;");
1809 
1810         return method;
1811     }
1812 }