View Javadoc
1   package org.codehaus.modello.plugin.snakeyaml;
2   
3   /*
4    * Copyright (c) 2004-2013, 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.List;
29  import java.util.Properties;
30  
31  import org.codehaus.modello.ModelloException;
32  import org.codehaus.modello.model.Model;
33  import org.codehaus.modello.model.ModelAssociation;
34  import org.codehaus.modello.model.ModelClass;
35  import org.codehaus.modello.model.ModelDefault;
36  import org.codehaus.modello.model.ModelField;
37  import org.codehaus.modello.plugin.java.javasource.JClass;
38  import org.codehaus.modello.plugin.java.javasource.JMethod;
39  import org.codehaus.modello.plugin.java.javasource.JParameter;
40  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
41  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
42  import org.codehaus.modello.plugin.java.javasource.JType;
43  import org.codehaus.modello.plugin.java.metadata.JavaClassMetadata;
44  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
45  import org.codehaus.modello.plugin.model.ModelClassMetadata;
46  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
47  import org.codehaus.modello.plugins.xml.metadata.XmlClassMetadata;
48  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
49  import org.codehaus.plexus.util.StringUtils;
50  
51  /**
52   * @author <a href="mailto:simonetripodi@apache.org">Simone Tripodi</a>
53   */
54  @Named("snakeyaml-reader")
55  public class SnakeYamlReaderGenerator extends AbstractSnakeYamlGenerator {
56  
57      private static final String SOURCE_PARAM = "source";
58  
59      private static final String LOCATION_VAR = "_location";
60  
61      private ModelClass locationTracker;
62  
63      private String locationField;
64  
65      private ModelClass sourceTracker;
66  
67      private String trackingArgs;
68  
69      protected boolean isLocationTracking() {
70          return false;
71      }
72  
73      public void generate(Model model, Properties parameters) throws ModelloException {
74          initialize(model, parameters);
75  
76          locationTracker = sourceTracker = null;
77          trackingArgs = locationField = "";
78  
79          if (isLocationTracking()) {
80              locationTracker = model.getLocationTracker(getGeneratedVersion());
81              if (locationTracker == null) {
82                  throw new ModelloException("No model class has been marked as location tracker"
83                          + " via the attribute locationTracker=\"locations\""
84                          + ", cannot generate extended reader.");
85              }
86  
87              locationField =
88                      ((ModelClassMetadata) locationTracker.getMetadata(ModelClassMetadata.ID)).getLocationTracker();
89  
90              sourceTracker = model.getSourceTracker(getGeneratedVersion());
91  
92              if (sourceTracker != null) {
93                  trackingArgs += ", " + SOURCE_PARAM;
94              }
95          }
96  
97          try {
98              generateSnakeYamlReader();
99          } catch (IOException ex) {
100             throw new ModelloException("Exception while generating SnakeYaml Reader.", ex);
101         }
102     }
103 
104     private void writeAllClassesReaders(Model objectModel, JClass jClass) {
105         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
106 
107         for (ModelClass clazz : getClasses(objectModel)) {
108             if (isTrackingSupport(clazz)) {
109                 continue;
110             }
111 
112             writeClassReaders(clazz, jClass, root.getName().equals(clazz.getName()));
113         }
114     }
115 
116     private void writeClassReaders(ModelClass modelClass, JClass jClass, boolean rootElement) {
117         JavaClassMetadata javaClassMetadata =
118                 (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.class.getName());
119 
120         // Skip abstract classes, no way to parse them out into objects
121         if (javaClassMetadata.isAbstract()) {
122             return;
123         }
124 
125         XmlClassMetadata xmlClassMetadata = (XmlClassMetadata) modelClass.getMetadata(XmlClassMetadata.ID);
126         if (!rootElement && !xmlClassMetadata.isStandaloneRead()) {
127             return;
128         }
129 
130         String className = modelClass.getName();
131 
132         String capClassName = capitalise(className);
133 
134         String readerMethodName = "read";
135         if (!rootElement) {
136             readerMethodName += capClassName;
137         }
138 
139         // ----------------------------------------------------------------------
140         // Write the read(Parser) method which will do the unmarshalling.
141         // ----------------------------------------------------------------------
142 
143         JMethod unmarshall = new JMethod(readerMethodName, new JClass(className), null);
144         unmarshall.getModifiers().makePrivate();
145 
146         unmarshall.addParameter(new JParameter(new JClass("Parser"), "parser"));
147         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
148         addTrackingParameters(unmarshall);
149 
150         unmarshall.addException(new JClass("IOException"));
151 
152         JSourceCode sc = unmarshall.getSourceCode();
153 
154         String variableName = uncapitalise(className);
155 
156         sc.add("Event event;");
157 
158         sc.add("if ( !( event = parser.getEvent() ).is( Event.ID.StreamStart ) )");
159         sc.add("{");
160         sc.addIndented(
161                 "throw new ParserException( \"Expected Stream Start event\", event.getStartMark(), \"\", null );");
162         sc.add("}");
163 
164         sc.add("if ( !( event = parser.getEvent() ).is( Event.ID.DocumentStart ) )");
165         sc.add("{");
166         sc.addIndented(
167                 "throw new ParserException( \"Expected Document Start event\", event.getStartMark(), \"\", null );");
168         sc.add("}");
169 
170         sc.add("");
171 
172         sc.add(className + ' ' + variableName + " = parse" + capClassName + "( parser, strict" + trackingArgs + " );");
173 
174         if (rootElement) {
175             // TODO
176             // sc.add( variableName + ".setModelEncoding( parser.getInputEncoding() );" );
177         }
178 
179         sc.add("");
180 
181         sc.add("if ( !( event = parser.getEvent() ).is( Event.ID.DocumentEnd ) )");
182         sc.add("{");
183         sc.addIndented(
184                 "throw new ParserException( \"Expected Document End event\", event.getStartMark(), \"\", null );");
185         sc.add("}");
186 
187         sc.add("if ( !( event = parser.getEvent() ).is( Event.ID.StreamEnd ) )");
188         sc.add("{");
189         sc.addIndented("throw new ParserException( \"Expected Stream End event\", event.getStartMark(), \"\", null );");
190         sc.add("}");
191 
192         sc.add("");
193 
194         sc.add("return " + variableName + ';');
195 
196         jClass.addMethod(unmarshall);
197 
198         // ----------------------------------------------------------------------
199         // Write the read(Reader[,boolean]) methods which will do the unmarshalling.
200         // ----------------------------------------------------------------------
201 
202         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
203 
204         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
205         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
206         addTrackingParameters(unmarshall);
207 
208         unmarshall.addException(new JClass("IOException"));
209 
210         sc = unmarshall.getSourceCode();
211 
212         sc.add("Parser parser = new ParserImpl( new StreamReader( reader ) );");
213 
214         sc.add("return " + readerMethodName + "( parser, strict );");
215 
216         jClass.addMethod(unmarshall);
217 
218         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
219 
220         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
221 
222         unmarshall.addException(new JClass("IOException"));
223 
224         sc = unmarshall.getSourceCode();
225         sc.add("return " + readerMethodName + "( reader, true );");
226 
227         jClass.addMethod(unmarshall);
228 
229         // ----------------------------------------------------------------------
230         // Write the read(InputStream[,boolean]) methods which will do the unmarshalling.
231         // ----------------------------------------------------------------------
232 
233         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
234 
235         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "in"));
236         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
237         addTrackingParameters(unmarshall);
238 
239         unmarshall.addException(new JClass("IOException"));
240 
241         sc = unmarshall.getSourceCode();
242 
243         sc.add("return " + readerMethodName + "( new InputStreamReader( in ), strict" + trackingArgs + " );");
244 
245         jClass.addMethod(unmarshall);
246         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
247 
248         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "in"));
249 
250         unmarshall.addException(new JClass("IOException"));
251 
252         sc = unmarshall.getSourceCode();
253 
254         sc.add("return " + readerMethodName + "( in, true );");
255 
256         jClass.addMethod(unmarshall);
257 
258         // --------------------------------------------------------------------
259     }
260 
261     private void generateSnakeYamlReader() throws ModelloException, IOException {
262         Model objectModel = getModel();
263 
264         String packageName =
265                 objectModel.getDefaultPackageName(isPackageWithVersion(), getGeneratedVersion()) + ".io.snakeyaml";
266 
267         String unmarshallerName = getFileName("SnakeYamlReader" + (isLocationTracking() ? "Ex" : ""));
268 
269         JSourceWriter sourceWriter = newJSourceWriter(packageName, unmarshallerName);
270 
271         JClass jClass = new JClass(packageName + '.' + unmarshallerName);
272         initHeader(jClass);
273         suppressAllWarnings(objectModel, jClass);
274 
275         jClass.addImport("org.yaml.snakeyaml.events.DocumentEndEvent");
276         jClass.addImport("org.yaml.snakeyaml.events.DocumentStartEvent");
277         jClass.addImport("org.yaml.snakeyaml.events.Event");
278         jClass.addImport("org.yaml.snakeyaml.events.ImplicitTuple");
279         jClass.addImport("org.yaml.snakeyaml.events.MappingEndEvent");
280         jClass.addImport("org.yaml.snakeyaml.events.MappingStartEvent");
281         jClass.addImport("org.yaml.snakeyaml.events.ScalarEvent");
282         jClass.addImport("org.yaml.snakeyaml.events.SequenceEndEvent");
283         jClass.addImport("org.yaml.snakeyaml.events.SequenceStartEvent");
284         jClass.addImport("org.yaml.snakeyaml.events.StreamEndEvent");
285         jClass.addImport("org.yaml.snakeyaml.events.StreamStartEvent");
286         jClass.addImport("org.yaml.snakeyaml.parser.Parser");
287         jClass.addImport("org.yaml.snakeyaml.parser.ParserException");
288         jClass.addImport("org.yaml.snakeyaml.parser.ParserImpl");
289         jClass.addImport("org.yaml.snakeyaml.reader.StreamReader");
290         jClass.addImport("java.io.InputStream");
291         jClass.addImport("java.io.InputStreamReader");
292         jClass.addImport("java.io.IOException");
293         jClass.addImport("java.io.Reader");
294         jClass.addImport("java.text.DateFormat");
295         jClass.addImport("java.util.Set");
296         jClass.addImport("java.util.HashSet");
297 
298         addModelImports(jClass, null);
299 
300         // ----------------------------------------------------------------------
301         // Write the class parsers
302         // ----------------------------------------------------------------------
303 
304         writeAllClassesParser(objectModel, jClass);
305 
306         // ----------------------------------------------------------------------
307         // Write the class readers
308         // ----------------------------------------------------------------------
309 
310         writeAllClassesReaders(objectModel, jClass);
311 
312         // ----------------------------------------------------------------------
313         // Write helpers
314         // ----------------------------------------------------------------------
315 
316         writeHelpers(jClass);
317 
318         // ----------------------------------------------------------------------
319         //
320         // ----------------------------------------------------------------------
321 
322         jClass.print(sourceWriter);
323 
324         sourceWriter.close();
325     }
326 
327     private void writeAllClassesParser(Model objectModel, JClass jClass) {
328         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
329 
330         for (ModelClass clazz : getClasses(objectModel)) {
331             if (isTrackingSupport(clazz)) {
332                 continue;
333             }
334 
335             writeClassParser(clazz, jClass, root.getName().equals(clazz.getName()));
336         }
337     }
338 
339     private void writeClassParser(ModelClass modelClass, JClass jClass, boolean rootElement) {
340         JavaClassMetadata javaClassMetadata =
341                 (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.class.getName());
342 
343         // Skip abstract classes, no way to parse them out into objects
344         if (javaClassMetadata.isAbstract()) {
345             return;
346         }
347 
348         String className = modelClass.getName();
349 
350         String capClassName = capitalise(className);
351 
352         String uncapClassName = uncapitalise(className);
353 
354         JMethod unmarshall = new JMethod("parse" + capClassName, new JClass(className), null);
355         unmarshall.getModifiers().makePrivate();
356 
357         unmarshall.addParameter(new JParameter(new JClass("Parser"), "parser"));
358         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
359         addTrackingParameters(unmarshall);
360 
361         unmarshall.addException(new JClass("IOException"));
362 
363         JSourceCode sc = unmarshall.getSourceCode();
364 
365         sc.add("Event event = parser.getEvent();");
366 
367         sc.add("");
368 
369         sc.add("if ( !event.is( Event.ID.MappingStart ) )");
370         sc.add("{");
371         sc.addIndented("throw new ParserException( \"Expected '"
372                 + className
373                 + "' data to start with a Mapping\", event.getStartMark(), \"\", null );");
374         sc.add("}");
375 
376         sc.add("");
377 
378         sc.add(className + " " + uncapClassName + " = new " + className + "();");
379 
380         if (locationTracker != null) {
381             sc.add(locationTracker.getName() + " " + LOCATION_VAR + ";");
382             writeNewSetLocation("\"\"", uncapClassName, null, sc);
383         }
384 
385         ModelField contentField = null;
386 
387         List<ModelField> modelFields = getFieldsForXml(modelClass, getGeneratedVersion());
388 
389         // read all XML attributes first
390         contentField = writeClassAttributesParser(modelFields, uncapClassName, rootElement);
391 
392         // then read content, either content field or elements
393         if (contentField != null) {
394             writePrimitiveField(
395                     contentField,
396                     contentField.getType(),
397                     uncapClassName,
398                     uncapClassName,
399                     "\"\"",
400                     "set" + capitalise(contentField.getName()),
401                     sc,
402                     false);
403         } else {
404             // Write other fields
405 
406             sc.add("Set<String> parsed = new HashSet<String>();");
407 
408             sc.add("");
409 
410             sc.add("while ( !( event = parser.getEvent() ).is( Event.ID.MappingEnd ) )");
411 
412             sc.add("{");
413             sc.indent();
414 
415             boolean addElse = false;
416 
417             for (ModelField field : modelFields) {
418                 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
419 
420                 processField(field, xmlFieldMetadata, addElse, sc, uncapClassName, jClass);
421 
422                 addElse = true;
423             }
424 
425             if (addElse) {
426                 sc.add("else");
427 
428                 sc.add("{");
429                 sc.indent();
430             }
431 
432             sc.add("checkUnknownElement( event, parser, strict );");
433 
434             if (addElse) {
435                 sc.unindent();
436                 sc.add("}");
437             }
438 
439             sc.unindent();
440             sc.add("}");
441         }
442 
443         sc.add("return " + uncapClassName + ";");
444 
445         jClass.addMethod(unmarshall);
446     }
447 
448     private ModelField writeClassAttributesParser(
449             List<ModelField> modelFields, String objectName, boolean rootElement) {
450         ModelField contentField = null;
451 
452         for (ModelField field : modelFields) {
453             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
454 
455             // TODO check if we have already one with this type and throws Exception
456             if (xmlFieldMetadata.isContent()) {
457                 contentField = field;
458             }
459         }
460 
461         return contentField;
462     }
463 
464     /**
465      * Generate code to process a field represented as an XML element.
466      *
467      * @param field            the field to process
468      * @param xmlFieldMetadata its XML metadata
469      * @param addElse          add an <code>else</code> statement before generating a new <code>if</code>
470      * @param sc               the method source code to add to
471      * @param objectName       the object name in the source
472      * @param jClass           the generated class source file
473      */
474     private void processField(
475             ModelField field,
476             XmlFieldMetadata xmlFieldMetadata,
477             boolean addElse,
478             JSourceCode sc,
479             String objectName,
480             JClass jClass) {
481         String fieldTagName = resolveTagName(field, xmlFieldMetadata);
482 
483         String capFieldName = capitalise(field.getName());
484 
485         String singularName = singular(field.getName());
486 
487         String alias;
488         if (StringUtils.isEmpty(field.getAlias())) {
489             alias = "null";
490         } else {
491             alias = "\"" + field.getAlias() + "\"";
492         }
493 
494         String tagComparison = (addElse ? "else " : "") + "if ( checkFieldWithDuplicate( event, \"" + fieldTagName
495                 + "\", " + alias + ", parsed ) )";
496 
497         if (!(field instanceof ModelAssociation)) { // model field
498             sc.add(tagComparison);
499 
500             sc.add("{");
501             sc.indent();
502 
503             writePrimitiveField(
504                     field,
505                     field.getType(),
506                     objectName,
507                     objectName,
508                     "\"" + field.getName() + "\"",
509                     "set" + capFieldName,
510                     sc,
511                     false);
512 
513             sc.unindent();
514             sc.add("}");
515         } else { // model association
516             ModelAssociation association = (ModelAssociation) field;
517 
518             String associationName = association.getName();
519 
520             if (association.isOneMultiplicity()) {
521                 sc.add(tagComparison);
522 
523                 sc.add("{");
524                 sc.indent();
525 
526                 // sc.add( "// consume current key" );
527                 // sc.add( "parser.getEvent();" );
528                 sc.add(objectName
529                         + ".set"
530                         + capFieldName
531                         + "( parse"
532                         + association.getTo()
533                         + "( parser, strict"
534                         + trackingArgs
535                         + " ) );");
536 
537                 sc.unindent();
538                 sc.add("}");
539             } else {
540                 // MANY_MULTIPLICITY
541 
542                 XmlAssociationMetadata xmlAssociationMetadata =
543                         (XmlAssociationMetadata) association.getAssociationMetadata(XmlAssociationMetadata.ID);
544 
545                 String type = association.getType();
546 
547                 if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
548                     boolean inModel = isClassInModel(
549                             association.getTo(), field.getModelClass().getModel());
550 
551                     sc.add((addElse ? "else " : "")
552                             + "if ( checkFieldWithDuplicate( event, \""
553                             + fieldTagName
554                             + "\", "
555                             + alias
556                             + ", parsed ) )");
557 
558                     sc.add("{");
559                     sc.indent();
560 
561                     sc.add("if ( !parser.getEvent().is( Event.ID.SequenceStart ) )");
562                     sc.add("{");
563                     sc.addIndented("throw new ParserException( \"Expected '"
564                             + field.getName()
565                             + "' data to start with a Sequence\", event.getStartMark(), \"\", null );");
566                     sc.add("}");
567 
568                     JavaFieldMetadata javaFieldMetadata =
569                             (JavaFieldMetadata) association.getMetadata(JavaFieldMetadata.ID);
570 
571                     String adder;
572 
573                     if (javaFieldMetadata.isGetter() && javaFieldMetadata.isSetter()) {
574                         sc.add(type + " " + associationName + " = " + objectName + ".get" + capFieldName + "();");
575 
576                         sc.add("if ( " + associationName + " == null )");
577 
578                         sc.add("{");
579                         sc.indent();
580 
581                         sc.add(associationName + " = " + association.getDefaultValue() + ";");
582 
583                         sc.add(objectName + ".set" + capFieldName + "( " + associationName + " );");
584 
585                         sc.unindent();
586                         sc.add("}");
587 
588                         adder = associationName + ".add";
589                     } else {
590                         adder = objectName + ".add" + association.getTo();
591                     }
592 
593                     if (!inModel && locationTracker != null) {
594                         sc.add(locationTracker.getName() + " " + LOCATION_VAR + "s = " + objectName + ".get"
595                                 + capitalise(singular(locationField)) + "( \"" + field.getName()
596                                 + "\" );");
597                         sc.add("if ( " + LOCATION_VAR + "s == null )");
598                         sc.add("{");
599                         sc.indent();
600                         writeNewSetLocation(field, objectName, LOCATION_VAR + "s", sc);
601                         sc.unindent();
602                         sc.add("}");
603                     }
604 
605                     if (inModel) {
606                         sc.add("while ( !parser.peekEvent().is( Event.ID.SequenceEnd ) )");
607                         sc.add("{");
608 
609                         sc.addIndented(
610                                 adder + "( parse" + association.getTo() + "( parser, strict" + trackingArgs + " ) );");
611 
612                         sc.add("}");
613 
614                         sc.add("parser.getEvent();");
615                     } else {
616                         String key;
617                         if (ModelDefault.SET.equals(type)) {
618                             key = "?";
619                         } else {
620                             key = (hasJavaSourceSupport(5) ? "Integer.valueOf" : "new java.lang.Integer") + "( "
621                                     + associationName + ".size() )";
622                         }
623                         writePrimitiveField(
624                                 association,
625                                 association.getTo(),
626                                 associationName,
627                                 LOCATION_VAR + "s",
628                                 key,
629                                 "add",
630                                 sc,
631                                 false);
632                     }
633 
634                     sc.unindent();
635                     sc.add("}");
636                 } else {
637                     // Map or Properties
638 
639                     sc.add(tagComparison);
640 
641                     sc.add("{");
642                     sc.indent();
643 
644                     if (locationTracker != null) {
645                         sc.add(locationTracker.getName() + " " + LOCATION_VAR + "s;");
646                         writeNewSetLocation(field, objectName, LOCATION_VAR + "s", sc);
647                     }
648 
649                     if (xmlAssociationMetadata.isMapExplode()) {
650                         sc.add("if ( !parser.getEvent().is( Event.ID.SequenceStart ) )");
651                         sc.add("{");
652                         sc.addIndented("throw new ParserException( \"Expected '"
653                                 + field.getName()
654                                 + "' data to start with a Sequence\", event.getStartMark(), \"\", null );");
655                         sc.add("}");
656 
657                         sc.add("while ( !parser.peekEvent().is( Event.ID.SequenceEnd ) )");
658 
659                         sc.add("{");
660                         sc.indent();
661 
662                         sc.add("event = parser.getEvent();");
663 
664                         sc.add("");
665 
666                         sc.add("if ( !event.is( Event.ID.MappingStart ) )");
667                         sc.add("{");
668                         sc.addIndented("throw new ParserException( \"Expected '"
669                                 + fieldTagName
670                                 + "' item data to start with a Mapping\", event.getStartMark(), \"\", null );");
671                         sc.add("}");
672 
673                         sc.add("String key = null;");
674 
675                         sc.add("String value = null;");
676 
677                         sc.add("Set<String> parsedPropertiesElements = new HashSet<String>();");
678 
679                         sc.add("while ( !( event = parser.getEvent() ).is( Event.ID.MappingEnd ) )");
680 
681                         sc.add("{");
682                         sc.indent();
683 
684                         sc.add("if ( checkFieldWithDuplicate( event, \"key\", \"\", parsedPropertiesElements ) )");
685                         sc.add("{");
686 
687                         String parserGetter = "( (ScalarEvent) parser.getEvent() ).getValue()";
688                         if (xmlFieldMetadata.isTrim()) {
689                             parserGetter = "getTrimmedValue( " + parserGetter + " )";
690                         }
691 
692                         sc.addIndented("key = " + parserGetter + ";");
693 
694                         sc.add("}");
695                         sc.add(
696                                 "else if ( checkFieldWithDuplicate( event, \"value\", \"\", parsedPropertiesElements ) )");
697                         sc.add("{");
698 
699                         parserGetter = "( (ScalarEvent) parser.getEvent() ).getValue()";
700                         if (xmlFieldMetadata.isTrim()) {
701                             parserGetter = "getTrimmedValue( " + parserGetter + " )";
702                         }
703 
704                         sc.addIndented("value = " + parserGetter + ";");
705 
706                         sc.add("}");
707 
708                         sc.add("else");
709 
710                         sc.add("{");
711 
712                         sc.addIndented("checkUnknownElement( event, parser, strict );");
713 
714                         sc.add("}");
715 
716                         sc.unindent();
717                         sc.add("}");
718 
719                         sc.add(objectName + ".add" + capitalise(singularName) + "( key, value );");
720 
721                         sc.unindent();
722                         sc.add("}");
723                     } else {
724                         // INLINE Mode
725 
726                         sc.add("if ( !parser.getEvent().is( Event.ID.MappingStart ) )");
727                         sc.add("{");
728                         sc.addIndented("throw new ParserException( \"Expected '"
729                                 + field.getName()
730                                 + "' data to start with a Mapping\", event.getStartMark(), \"\", null );");
731                         sc.add("}");
732 
733                         sc.add("while ( !parser.peekEvent().is( Event.ID.MappingEnd ) )");
734 
735                         sc.add("{");
736                         sc.indent();
737 
738                         sc.add("String key = ( (ScalarEvent) parser.getEvent() ).getValue();");
739 
740                         writeNewSetLocation("key", LOCATION_VAR + "s", null, sc);
741 
742                         sc.add("String value = ( (ScalarEvent) parser.getEvent() ).getValue()"
743                                 + (xmlFieldMetadata.isTrim() ? ".trim()" : "") + ";");
744 
745                         sc.add(objectName + ".add" + capitalise(singularName) + "( key, value );");
746 
747                         sc.unindent();
748                         sc.add("}");
749                     }
750 
751                     sc.add("parser.getEvent();");
752 
753                     sc.unindent();
754                     sc.add("}");
755                 }
756             }
757         }
758     }
759 
760     private void writeHelpers(JClass jClass) {
761         JMethod method = new JMethod("getTrimmedValue", new JClass("String"), null);
762         method.getModifiers().makePrivate();
763 
764         method.addParameter(new JParameter(new JClass("String"), "s"));
765 
766         JSourceCode sc = method.getSourceCode();
767 
768         sc.add("if ( s != null )");
769 
770         sc.add("{");
771         sc.addIndented("s = s.trim();");
772         sc.add("}");
773 
774         sc.add("return s;");
775 
776         jClass.addMethod(method);
777 
778         // --------------------------------------------------------------------
779 
780         method = new JMethod("getRequiredAttributeValue", new JClass("String"), null);
781         method.addException(new JClass("ParserException"));
782         method.getModifiers().makePrivate();
783 
784         method.addParameter(new JParameter(new JClass("String"), "s"));
785         method.addParameter(new JParameter(new JClass("String"), "attribute"));
786         method.addParameter(new JParameter(new JClass("Parser"), "parser"));
787         method.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
788 
789         sc = method.getSourceCode();
790 
791         sc.add("if ( s == null )");
792 
793         sc.add("{");
794         sc.indent();
795 
796         sc.add("if ( strict )");
797 
798         sc.add("{");
799         sc.addIndented(
800                 "throw new ParserException( \"Missing required value for attribute '\" + attribute + \"'\", parser.peekEvent().getStartMark(), \"\", null );");
801         sc.add("}");
802 
803         sc.unindent();
804         sc.add("}");
805 
806         sc.add("return s;");
807 
808         jClass.addMethod(method);
809 
810         // --------------------------------------------------------------------
811 
812         method = new JMethod("checkFieldWithDuplicate", JType.BOOLEAN, null);
813         method.getModifiers().makePrivate();
814 
815         method.addParameter(new JParameter(new JClass("Event"), "event"));
816         method.addParameter(new JParameter(new JClass("String"), "tagName"));
817         method.addParameter(new JParameter(new JClass("String"), "alias"));
818         method.addParameter(new JParameter(new JClass("Set"), "parsed"));
819         method.addException(new JClass("IOException"));
820 
821         sc = method.getSourceCode();
822 
823         sc.add("String currentName = ( (ScalarEvent) event ).getValue();");
824 
825         sc.add("");
826 
827         sc.add("if ( !( currentName.equals( tagName ) || currentName.equals( alias ) ) )");
828 
829         sc.add("{");
830         sc.addIndented("return false;");
831         sc.add("}");
832 
833         sc.add("if ( !parsed.add( tagName ) )");
834 
835         sc.add("{");
836         sc.addIndented(
837                 "throw new ParserException( \"Duplicated tag: '\" + tagName + \"'\", event.getStartMark(), \"\", null );");
838         sc.add("}");
839 
840         sc.add("return true;");
841 
842         jClass.addMethod(method);
843 
844         // --------------------------------------------------------------------
845 
846         method = new JMethod("checkUnknownElement", null, null);
847         method.getModifiers().makePrivate();
848 
849         method.addParameter(new JParameter(new JClass("Event"), "event"));
850         method.addParameter(new JParameter(new JClass("Parser"), "parser"));
851         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
852         method.addException(new JClass("IOException"));
853 
854         sc = method.getSourceCode();
855 
856         sc.add("if ( strict )");
857 
858         sc.add("{");
859         sc.addIndented(
860                 "throw new ParserException( \"Unrecognised tag: '\" + ( (ScalarEvent) event ).getValue() + \"'\", event.getStartMark(), \"\", null );");
861         sc.add("}");
862 
863         sc.add("");
864 
865         sc.add("for ( int unrecognizedTagCount = 1; unrecognizedTagCount > 0; )");
866         sc.add("{");
867         sc.indent();
868         sc.add("event = parser.getEvent();");
869         sc.add("if ( event.is( Event.ID.MappingStart ) )");
870         sc.add("{");
871         sc.addIndented("unrecognizedTagCount++;");
872         sc.add("}");
873         sc.add("else if ( event.is( Event.ID.MappingEnd ) )");
874         sc.add("{");
875         sc.addIndented("unrecognizedTagCount--;");
876         sc.add("}");
877         sc.unindent();
878         sc.add("}");
879 
880         jClass.addMethod(method);
881 
882         // --------------------------------------------------------------------
883 
884         method = new JMethod("checkUnknownAttribute", null, null);
885         method.getModifiers().makePrivate();
886 
887         method.addParameter(new JParameter(new JClass("Parser"), "parser"));
888         method.addParameter(new JParameter(new JClass("String"), "attribute"));
889         method.addParameter(new JParameter(new JClass("String"), "tagName"));
890         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
891         method.addException(new JClass("IOException"));
892 
893         sc = method.getSourceCode();
894 
895         if (strictXmlAttributes) {
896             sc.add(
897                     "// strictXmlAttributes = true for model: if strict == true, not only elements are checked but attributes too");
898             sc.add("if ( strict )");
899 
900             sc.add("{");
901             sc.addIndented(
902                     "throw new ParserException( \"\", parser.peekEvent().getStartMark(), \"Unknown attribute '\" + attribute + \"' for tag '\" + tagName + \"'\", parser.peekEvent().getEndMark() );");
903             sc.add("}");
904         } else {
905             sc.add(
906                     "// strictXmlAttributes = false for model: always ignore unknown XML attribute, even if strict == true");
907         }
908 
909         jClass.addMethod(method);
910 
911         // --------------------------------------------------------------------
912 
913         method = new JMethod("getBooleanValue", JType.BOOLEAN, null);
914         method.getModifiers().makePrivate();
915 
916         method.addParameter(new JParameter(new JClass("String"), "s"));
917 
918         sc = method.getSourceCode();
919 
920         sc.add("if ( s != null )");
921 
922         sc.add("{");
923         sc.addIndented("return Boolean.valueOf( s ).booleanValue();");
924         sc.add("}");
925 
926         sc.add("return false;");
927 
928         jClass.addMethod(method);
929 
930         // --------------------------------------------------------------------
931 
932         method = new JMethod("getCharacterValue", JType.CHAR, null);
933         method.getModifiers().makePrivate();
934 
935         method.addParameter(new JParameter(new JClass("String"), "s"));
936 
937         sc = method.getSourceCode();
938 
939         sc.add("if ( s != null )");
940 
941         sc.add("{");
942         sc.addIndented("return s.charAt( 0 );");
943         sc.add("}");
944 
945         sc.add("return 0;");
946 
947         jClass.addMethod(method);
948 
949         // --------------------------------------------------------------------
950 
951         method = convertNumericalType("getIntegerValue", JType.INT, "Integer.valueOf( s ).intValue()", "an integer");
952 
953         jClass.addMethod(method);
954 
955         // --------------------------------------------------------------------
956 
957         method = convertNumericalType(
958                 "getShortValue", JType.SHORT, "Short.valueOf( s ).shortValue()", "a short integer");
959 
960         jClass.addMethod(method);
961 
962         // --------------------------------------------------------------------
963 
964         method = convertNumericalType("getByteValue", JType.BYTE, "Byte.valueOf( s ).byteValue()", "a byte");
965 
966         jClass.addMethod(method);
967 
968         // --------------------------------------------------------------------
969 
970         method = convertNumericalType("getLongValue", JType.LONG, "Long.valueOf( s ).longValue()", "a long integer");
971 
972         jClass.addMethod(method);
973 
974         // --------------------------------------------------------------------
975 
976         method = convertNumericalType(
977                 "getFloatValue", JType.FLOAT, "Float.valueOf( s ).floatValue()", "a floating point number");
978 
979         jClass.addMethod(method);
980 
981         // --------------------------------------------------------------------
982 
983         method = convertNumericalType(
984                 "getDoubleValue", JType.DOUBLE, "Double.valueOf( s ).doubleValue()", "a floating point number");
985 
986         jClass.addMethod(method);
987 
988         // --------------------------------------------------------------------
989 
990         method = new JMethod("getDateValue", new JClass("java.util.Date"), null);
991         method.getModifiers().makePrivate();
992 
993         method.addParameter(new JParameter(new JClass("String"), "s"));
994         method.addParameter(new JParameter(new JClass("String"), "dateFormat"));
995         method.addParameter(new JParameter(new JClass("Event"), "event"));
996 
997         writeDateParsingHelper(
998                 method.getSourceCode(),
999                 "new ParserException( \"\", event.getStartMark(), e.getMessage(), event.getEndMark() )");
1000 
1001         jClass.addMethod(method);
1002 
1003         // --------------------------------------------------------------------
1004 
1005         method = new JMethod("getDefaultValue", new JClass("String"), null);
1006         method.getModifiers().makePrivate();
1007 
1008         method.addParameter(new JParameter(new JClass("String"), "s"));
1009         method.addParameter(new JParameter(new JClass("String"), "v"));
1010 
1011         sc = method.getSourceCode();
1012 
1013         sc.add("if ( s == null )");
1014 
1015         sc.add("{");
1016         sc.addIndented("s = v;");
1017         sc.add("}");
1018 
1019         sc.add("return s;");
1020 
1021         jClass.addMethod(method);
1022     }
1023 
1024     private void addTrackingParameters(JMethod method) {
1025         if (sourceTracker != null) {
1026             method.addParameter(new JParameter(new JClass(sourceTracker.getName()), SOURCE_PARAM));
1027         }
1028     }
1029 
1030     private void writeNewSetLocation(ModelField field, String objectName, String trackerVariable, JSourceCode sc) {
1031         writeNewSetLocation("\"" + field.getName() + "\"", objectName, trackerVariable, sc);
1032     }
1033 
1034     private void writeNewSetLocation(String key, String objectName, String trackerVariable, JSourceCode sc) {
1035         writeNewLocation(trackerVariable, sc);
1036         writeSetLocation(key, objectName, trackerVariable, sc);
1037     }
1038 
1039     private void writeNewLocation(String trackerVariable, JSourceCode sc) {
1040         if (locationTracker == null) {
1041             return;
1042         }
1043 
1044         String constr = "new " + locationTracker.getName() + "( parser.getLineNumber(), parser.getColumnNumber()";
1045         constr += (sourceTracker != null) ? ", " + SOURCE_PARAM : "";
1046         constr += " )";
1047 
1048         sc.add(((trackerVariable != null) ? trackerVariable : LOCATION_VAR) + " = " + constr + ";");
1049     }
1050 
1051     private void writeSetLocation(String key, String objectName, String trackerVariable, JSourceCode sc) {
1052         if (locationTracker == null) {
1053             return;
1054         }
1055 
1056         String variable = (trackerVariable != null) ? trackerVariable : LOCATION_VAR;
1057 
1058         sc.add(objectName + ".set" + capitalise(singular(locationField)) + "( " + key + ", " + variable + " );");
1059     }
1060 
1061     /**
1062      * Write code to set a primitive field with a value got from the parser, with appropriate default value, trimming
1063      * and required check logic.
1064      *
1065      * @param field the model field to set (either XML attribute or element)
1066      * @param type the type of the value read from XML
1067      * @param objectName the object name in source
1068      * @param setterName the setter method name
1069      * @param sc the source code to add to
1070      */
1071     private void writePrimitiveField(
1072             ModelField field,
1073             String type,
1074             String objectName,
1075             String locatorName,
1076             String locationKey,
1077             String setterName,
1078             JSourceCode sc,
1079             boolean wrappedItem) {
1080         XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
1081 
1082         String tagName = resolveTagName(field, xmlFieldMetadata);
1083 
1084         String parserGetter = "( (ScalarEvent) parser.getEvent() ).getValue()";
1085 
1086         /* TODO:
1087                 if ( xmlFieldMetadata.isRequired() )
1088                 {
1089                     parserGetter = "getRequiredAttributeValue( " + parserGetter + ", \"" + tagName + "\", parser, strict )";
1090                 }
1091         */
1092         if (field.getDefaultValue() != null) {
1093             parserGetter = "getDefaultValue( " + parserGetter + ", \"" + field.getDefaultValue() + "\" )";
1094         }
1095 
1096         if (xmlFieldMetadata.isTrim()) {
1097             parserGetter = "getTrimmedValue( " + parserGetter + " )";
1098         }
1099 
1100         if ("boolean".equals(type)) {
1101             sc.add(objectName + "." + setterName + "( getBooleanValue( " + parserGetter + " ) );");
1102         } else if ("char".equals(type)) {
1103             sc.add(objectName + "." + setterName + "( getCharacterValue( " + parserGetter + ", \"" + tagName
1104                     + "\" ) );");
1105         } else if ("double".equals(type)) {
1106             sc.add(objectName + "." + setterName + "( getDoubleValue( " + parserGetter + ", \"" + tagName
1107                     + "\", parser.peekEvent(), strict ) );");
1108         } else if ("float".equals(type)) {
1109             sc.add(objectName + "." + setterName + "( getFloatValue( " + parserGetter + ", \"" + tagName
1110                     + "\", parser.peekEvent(), strict ) );");
1111         } else if ("int".equals(type)) {
1112             sc.add(objectName + "." + setterName + "( getIntegerValue( " + parserGetter + ", \"" + tagName
1113                     + "\", parser.peekEvent(), strict ) );");
1114         } else if ("long".equals(type)) {
1115             sc.add(objectName + "." + setterName + "( getLongValue( " + parserGetter + ", \"" + tagName
1116                     + "\", parser.peekEvent(), strict ) );");
1117         } else if ("short".equals(type)) {
1118             sc.add(objectName + "." + setterName + "( getShortValue( " + parserGetter + ", \"" + tagName
1119                     + "\", parser.peekEvent(), strict ) );");
1120         } else if ("byte".equals(type)) {
1121             sc.add(objectName + "." + setterName + "( getByteValue( " + parserGetter + ", \"" + tagName
1122                     + "\", parser.peekEvent(), strict ) );");
1123         } else if ("String".equals(type) || "Boolean".equals(type)) {
1124             // TODO: other Primitive types
1125             sc.add(objectName + "." + setterName + "( " + parserGetter + " );");
1126         } else if ("Date".equals(type)) {
1127             sc.add("String dateFormat = "
1128                     + (xmlFieldMetadata.getFormat() != null ? "\"" + xmlFieldMetadata.getFormat() + "\"" : "null")
1129                     + ";");
1130             sc.add(objectName + "." + setterName + "( getDateValue( " + parserGetter + ", \"" + tagName
1131                     + "\", dateFormat, parser.peekEvent() ) );");
1132         } else {
1133             throw new IllegalArgumentException("Unknown type "
1134                     + type
1135                     + " for field "
1136                     + field.getModelClass().getName()
1137                     + "."
1138                     + field.getName());
1139         }
1140     }
1141 
1142     private JMethod convertNumericalType(String methodName, JType returnType, String expression, String typeDesc) {
1143         JMethod method = new JMethod(methodName, returnType, null);
1144         method.getModifiers().makePrivate();
1145 
1146         method.addParameter(new JParameter(new JClass("String"), "s"));
1147         method.addParameter(new JParameter(new JClass("String"), "attribute"));
1148         method.addParameter(new JParameter(new JClass("Event"), "event"));
1149         method.addParameter(new JParameter(JType.BOOLEAN, "strict"));
1150 
1151         JSourceCode sc = method.getSourceCode();
1152 
1153         sc.add("if ( s != null )");
1154 
1155         sc.add("{");
1156         sc.indent();
1157 
1158         sc.add("try");
1159 
1160         sc.add("{");
1161         sc.addIndented("return " + expression + ";");
1162         sc.add("}");
1163 
1164         sc.add("catch ( NumberFormatException nfe )");
1165 
1166         sc.add("{");
1167         sc.indent();
1168 
1169         sc.add("if ( strict )");
1170 
1171         sc.add("{");
1172         sc.addIndented(
1173                 "throw new ParserException( \"\", event.getStartMark(), \"Unable to parse element '\" + attribute + \"', must be "
1174                         + typeDesc
1175                         + " but was '\" + s + \"'\", event.getEndMark() );");
1176         sc.add("}");
1177 
1178         sc.unindent();
1179         sc.add("}");
1180 
1181         sc.unindent();
1182         sc.add("}");
1183 
1184         sc.add("return 0;");
1185 
1186         return method;
1187     }
1188 }