View Javadoc
1   package org.codehaus.modello.plugin.jackson;
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.Map;
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.JConstructor;
39  import org.codehaus.modello.plugin.java.javasource.JField;
40  import org.codehaus.modello.plugin.java.javasource.JMethod;
41  import org.codehaus.modello.plugin.java.javasource.JParameter;
42  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
43  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
44  import org.codehaus.modello.plugin.java.javasource.JType;
45  import org.codehaus.modello.plugin.java.metadata.JavaClassMetadata;
46  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
47  import org.codehaus.modello.plugin.model.ModelClassMetadata;
48  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
49  import org.codehaus.modello.plugins.xml.metadata.XmlClassMetadata;
50  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
51  import org.codehaus.plexus.util.StringUtils;
52  
53  /**
54   * @author <a href="mailto:simonetripodi@apache.org">Simone Tripodi</a>
55   */
56  @Named("jackson-reader")
57  public class JacksonReaderGenerator extends AbstractJacksonGenerator {
58  
59      private static final String SOURCE_PARAM = "source";
60  
61      private static final String LOCATION_VAR = "_location";
62  
63      private boolean requiresDomSupport;
64  
65      private ModelClass locationTracker;
66  
67      private String locationField;
68  
69      private ModelClass sourceTracker;
70  
71      private String trackingArgs;
72  
73      protected boolean isLocationTracking() {
74          return false;
75      }
76  
77      @Override
78      public void generate(Model model, Map<String, Object> parameters) throws ModelloException {
79          initialize(model, parameters);
80  
81          requiresDomSupport = false;
82          locationTracker = sourceTracker = null;
83          trackingArgs = locationField = "";
84  
85          if (isLocationTracking()) {
86              locationTracker = model.getLocationTracker(getGeneratedVersion());
87              if (locationTracker == null) {
88                  throw new ModelloException("No model class has been marked as location tracker"
89                          + " via the attribute locationTracker=\"locations\""
90                          + ", cannot generate extended reader.");
91              }
92  
93              locationField =
94                      ((ModelClassMetadata) locationTracker.getMetadata(ModelClassMetadata.ID)).getLocationTracker();
95  
96              sourceTracker = model.getSourceTracker(getGeneratedVersion());
97  
98              if (sourceTracker != null) {
99                  trackingArgs += ", " + SOURCE_PARAM;
100             }
101         }
102 
103         try {
104             generateJacksonReader();
105         } catch (IOException ex) {
106             throw new ModelloException("Exception while generating Jackson Reader.", ex);
107         }
108     }
109 
110     private void writeAllClassesReaders(Model objectModel, JClass jClass) {
111         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
112 
113         for (ModelClass clazz : getClasses(objectModel)) {
114             if (isTrackingSupport(clazz)) {
115                 continue;
116             }
117 
118             writeClassReaders(clazz, jClass, root.getName().equals(clazz.getName()));
119         }
120     }
121 
122     private void writeClassReaders(ModelClass modelClass, JClass jClass, boolean rootElement) {
123         JavaClassMetadata javaClassMetadata =
124                 (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.class.getName());
125 
126         // Skip abstract classes, no way to parse them out into objects
127         if (javaClassMetadata.isAbstract()) {
128             return;
129         }
130 
131         XmlClassMetadata xmlClassMetadata = (XmlClassMetadata) modelClass.getMetadata(XmlClassMetadata.ID);
132         if (!rootElement && !xmlClassMetadata.isStandaloneRead()) {
133             return;
134         }
135 
136         String className = modelClass.getName();
137 
138         String capClassName = capitalise(className);
139 
140         String readerMethodName = "read";
141         if (!rootElement) {
142             readerMethodName += capClassName;
143         }
144 
145         // ----------------------------------------------------------------------
146         // Write the read(JsonParser) method which will do the unmarshalling.
147         // ----------------------------------------------------------------------
148 
149         JMethod unmarshall = new JMethod(readerMethodName, new JClass(className), null);
150         unmarshall.getModifiers().makePrivate();
151 
152         unmarshall.addParameter(new JParameter(new JClass("JsonParser"), "parser"));
153         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
154         addTrackingParameters(unmarshall);
155 
156         unmarshall.addException(new JClass("IOException"));
157 
158         JSourceCode sc = unmarshall.getSourceCode();
159 
160         String variableName = uncapitalise(className);
161 
162         sc.add(className + ' ' + variableName + " = parse" + capClassName + "( parser, strict" + trackingArgs + " );");
163 
164         if (rootElement) {
165             // TODO
166             // sc.add( variableName + ".setModelEncoding( parser.getInputEncoding() );" );
167         }
168 
169         sc.add("return " + variableName + ';');
170 
171         jClass.addMethod(unmarshall);
172 
173         // ----------------------------------------------------------------------
174         // Write the read(Reader[,boolean]) methods which will do the unmarshalling.
175         // ----------------------------------------------------------------------
176 
177         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
178 
179         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
180         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
181         addTrackingParameters(unmarshall);
182 
183         unmarshall.addException(new JClass("IOException"));
184 
185         sc = unmarshall.getSourceCode();
186 
187         sc.add("JsonParser parser = factory.createParser( reader );");
188 
189         sc.add("return " + readerMethodName + "( parser, strict );");
190 
191         jClass.addMethod(unmarshall);
192         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
193 
194         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
195 
196         unmarshall.addException(new JClass("IOException"));
197 
198         sc = unmarshall.getSourceCode();
199         sc.add("return " + readerMethodName + "( reader, true );");
200 
201         jClass.addMethod(unmarshall);
202 
203         // ----------------------------------------------------------------------
204         // Write the read(InputStream[,boolean]) methods which will do the unmarshalling.
205         // ----------------------------------------------------------------------
206 
207         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
208 
209         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "in"));
210         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
211         addTrackingParameters(unmarshall);
212 
213         unmarshall.addException(new JClass("IOException"));
214 
215         sc = unmarshall.getSourceCode();
216 
217         sc.add("return " + readerMethodName + "( new InputStreamReader( in ), strict" + trackingArgs + " );");
218 
219         jClass.addMethod(unmarshall);
220         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
221 
222         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "in"));
223 
224         unmarshall.addException(new JClass("IOException"));
225 
226         sc = unmarshall.getSourceCode();
227 
228         sc.add("return " + readerMethodName + "( in, true );");
229 
230         jClass.addMethod(unmarshall);
231 
232         // --------------------------------------------------------------------
233     }
234 
235     private void generateJacksonReader() throws ModelloException, IOException {
236         Model objectModel = getModel();
237 
238         String packageName =
239                 objectModel.getDefaultPackageName(isPackageWithVersion(), getGeneratedVersion()) + ".io.jackson";
240 
241         String unmarshallerName = getFileName("JacksonReader" + (isLocationTracking() ? "Ex" : ""));
242 
243         JSourceWriter sourceWriter = newJSourceWriter(packageName, unmarshallerName);
244 
245         JClass jClass = new JClass(packageName + '.' + unmarshallerName);
246         initHeader(jClass);
247         suppressAllWarnings(objectModel, jClass);
248 
249         jClass.addImport("com.fasterxml.jackson.core.JsonFactory");
250         jClass.addImport("com.fasterxml.jackson.core.JsonParser");
251         jClass.addImport("com.fasterxml.jackson.core.JsonParser.Feature");
252         jClass.addImport("com.fasterxml.jackson.core.JsonParseException");
253         jClass.addImport("com.fasterxml.jackson.core.JsonToken");
254         jClass.addImport("java.io.InputStream");
255         jClass.addImport("java.io.InputStreamReader");
256         jClass.addImport("java.io.IOException");
257         jClass.addImport("java.io.Reader");
258         jClass.addImport("java.text.DateFormat");
259         jClass.addImport("java.util.Set");
260         jClass.addImport("java.util.HashSet");
261 
262         addModelImports(jClass, null);
263 
264         JField factoryField = new JField(new JClass("JsonFactory"), "factory");
265         factoryField.getModifiers().setFinal(true);
266         factoryField.setInitString("new JsonFactory()");
267         jClass.addField(factoryField);
268 
269         JConstructor jacksonReaderConstructor = new JConstructor(jClass);
270         JSourceCode sc = jacksonReaderConstructor.getSourceCode();
271         sc.add("factory.enable( Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER  );");
272         sc.add("factory.enable( Feature.ALLOW_COMMENTS );");
273         sc.add("factory.enable( Feature.ALLOW_NON_NUMERIC_NUMBERS );");
274         sc.add("factory.enable( Feature.ALLOW_NUMERIC_LEADING_ZEROS );");
275         sc.add("factory.enable( Feature.ALLOW_SINGLE_QUOTES );");
276         sc.add("factory.enable( Feature.ALLOW_UNQUOTED_CONTROL_CHARS );");
277         sc.add("factory.enable( Feature.ALLOW_UNQUOTED_FIELD_NAMES );");
278 
279         jClass.addConstructor(jacksonReaderConstructor);
280 
281         // ----------------------------------------------------------------------
282         // Write the class parsers
283         // ----------------------------------------------------------------------
284 
285         writeAllClassesParser(objectModel, jClass);
286 
287         // ----------------------------------------------------------------------
288         // Write the class readers
289         // ----------------------------------------------------------------------
290 
291         writeAllClassesReaders(objectModel, jClass);
292 
293         // ----------------------------------------------------------------------
294         // Write helpers
295         // ----------------------------------------------------------------------
296 
297         writeHelpers(jClass);
298 
299         // ----------------------------------------------------------------------
300         // DOM support
301         // ----------------------------------------------------------------------
302 
303         if (requiresDomSupport) {
304             jClass.addImport("com.fasterxml.jackson.databind.ObjectMapper");
305 
306             sc.add("factory.setCodec( new ObjectMapper() );");
307         }
308 
309         jClass.print(sourceWriter);
310 
311         sourceWriter.close();
312     }
313 
314     private void writeAllClassesParser(Model objectModel, JClass jClass) {
315         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
316 
317         for (ModelClass clazz : getClasses(objectModel)) {
318             if (isTrackingSupport(clazz)) {
319                 continue;
320             }
321 
322             writeClassParser(clazz, jClass, root.getName().equals(clazz.getName()));
323         }
324     }
325 
326     private void writeClassParser(ModelClass modelClass, JClass jClass, boolean rootElement) {
327         JavaClassMetadata javaClassMetadata =
328                 (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.class.getName());
329 
330         // Skip abstract classes, no way to parse them out into objects
331         if (javaClassMetadata.isAbstract()) {
332             return;
333         }
334 
335         String className = modelClass.getName();
336 
337         String capClassName = capitalise(className);
338 
339         String uncapClassName = uncapitalise(className);
340 
341         JMethod unmarshall = new JMethod("parse" + capClassName, new JClass(className), null);
342         unmarshall.getModifiers().makePrivate();
343 
344         unmarshall.addParameter(new JParameter(new JClass("JsonParser"), "parser"));
345         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
346         addTrackingParameters(unmarshall);
347 
348         unmarshall.addException(new JClass("IOException"));
349 
350         JSourceCode sc = unmarshall.getSourceCode();
351 
352         sc.add(
353                 "if ( JsonToken.START_OBJECT != parser.getCurrentToken() && JsonToken.START_OBJECT != parser.nextToken() )");
354         sc.add("{");
355         sc.addIndented("throw new JsonParseException( \"Expected '"
356                 + className
357                 + "' data to start with an Object\", parser.getCurrentLocation() );");
358         sc.add("}");
359 
360         sc.add(className + " " + uncapClassName + " = new " + className + "();");
361 
362         if (locationTracker != null) {
363             sc.add(locationTracker.getName() + " " + LOCATION_VAR + ";");
364             writeNewSetLocation("\"\"", uncapClassName, null, sc);
365         }
366 
367         List<ModelField> modelFields = getFieldsForXml(modelClass, getGeneratedVersion());
368 
369         {
370             // Write other fields
371 
372             sc.add("Set<String> parsed = new HashSet<String>();");
373 
374             sc.add("while ( JsonToken.END_OBJECT != parser.nextToken() )");
375 
376             sc.add("{");
377             sc.indent();
378 
379             boolean addElse = false;
380 
381             for (ModelField field : modelFields) {
382                 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
383 
384                 processField(field, xmlFieldMetadata, addElse, sc, uncapClassName, jClass);
385 
386                 addElse = true;
387             }
388 
389             if (addElse) {
390                 sc.add("else");
391 
392                 sc.add("{");
393                 sc.indent();
394             }
395 
396             sc.add("checkUnknownElement( parser, strict );");
397 
398             if (addElse) {
399                 sc.unindent();
400                 sc.add("}");
401             }
402 
403             sc.unindent();
404             sc.add("}");
405         }
406 
407         sc.add("return " + uncapClassName + ";");
408 
409         jClass.addMethod(unmarshall);
410     }
411 
412     /**
413      * Generate code to process a field represented as an XML element.
414      *
415      * @param field            the field to process
416      * @param xmlFieldMetadata its XML metadata
417      * @param addElse          add an <code>else</code> statement before generating a new <code>if</code>
418      * @param sc               the method source code to add to
419      * @param objectName       the object name in the source
420      * @param jClass           the generated class source file
421      */
422     private void processField(
423             ModelField field,
424             XmlFieldMetadata xmlFieldMetadata,
425             boolean addElse,
426             JSourceCode sc,
427             String objectName,
428             JClass jClass) {
429         String fieldTagName = resolveTagName(field, xmlFieldMetadata);
430 
431         String capFieldName = capitalise(field.getName());
432 
433         String singularName = singular(field.getName());
434 
435         String alias;
436         if (StringUtils.isEmpty(field.getAlias())) {
437             alias = "null";
438         } else {
439             alias = "\"" + field.getAlias() + "\"";
440         }
441 
442         String tagComparison = (addElse ? "else " : "") + "if ( checkFieldWithDuplicate( parser, \"" + fieldTagName
443                 + "\", " + alias + ", parsed ) )";
444 
445         if (!(field instanceof ModelAssociation)) { // model field
446             sc.add(tagComparison);
447 
448             sc.add("{");
449 
450             sc.indent();
451 
452             writePrimitiveField(
453                     field,
454                     field.getType(),
455                     objectName,
456                     objectName,
457                     "\"" + field.getName() + "\"",
458                     "set" + capFieldName,
459                     sc,
460                     false);
461 
462             sc.unindent();
463             sc.add("}");
464         } else { // model association
465             ModelAssociation association = (ModelAssociation) field;
466 
467             String associationName = association.getName();
468 
469             if (association.isOneMultiplicity()) {
470                 sc.add(tagComparison);
471 
472                 sc.add("{");
473                 sc.addIndented(objectName + ".set" + capFieldName + "( parse" + association.getTo() + "( parser, strict"
474                         + trackingArgs + " ) );");
475                 sc.add("}");
476             } else {
477                 // MANY_MULTIPLICITY
478 
479                 XmlAssociationMetadata xmlAssociationMetadata =
480                         (XmlAssociationMetadata) association.getAssociationMetadata(XmlAssociationMetadata.ID);
481 
482                 String type = association.getType();
483 
484                 if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
485                     boolean inModel = isClassInModel(
486                             association.getTo(), field.getModelClass().getModel());
487 
488                     sc.add((addElse ? "else " : "") + "if ( checkFieldWithDuplicate( parser, \""
489                             + fieldTagName
490                             + "\", "
491                             + alias
492                             + ", parsed ) )");
493 
494                     sc.add("{");
495                     sc.indent();
496 
497                     sc.add("if ( JsonToken.START_ARRAY != parser.nextToken() )");
498                     sc.add("{");
499                     sc.addIndented("throw new JsonParseException( \"Expected '"
500                             + fieldTagName
501                             + "' data to start with an Array\", parser.getCurrentLocation() );");
502                     sc.add("}");
503 
504                     JavaFieldMetadata javaFieldMetadata =
505                             (JavaFieldMetadata) association.getMetadata(JavaFieldMetadata.ID);
506 
507                     String adder;
508 
509                     if (javaFieldMetadata.isGetter() && javaFieldMetadata.isSetter()) {
510                         sc.add(type + " " + associationName + " = " + objectName + ".get" + capFieldName + "();");
511 
512                         sc.add("if ( " + associationName + " == null )");
513 
514                         sc.add("{");
515                         sc.indent();
516 
517                         sc.add(associationName + " = " + association.getDefaultValue() + ";");
518 
519                         sc.add(objectName + ".set" + capFieldName + "( " + associationName + " );");
520 
521                         sc.unindent();
522                         sc.add("}");
523 
524                         adder = associationName + ".add";
525                     } else {
526                         adder = objectName + ".add" + association.getTo();
527                     }
528 
529                     if (!inModel && locationTracker != null) {
530                         sc.add(locationTracker.getName() + " " + LOCATION_VAR + "s = " + objectName + ".get"
531                                 + capitalise(singular(locationField)) + "( \"" + field.getName()
532                                 + "\" );");
533                         sc.add("if ( " + LOCATION_VAR + "s == null )");
534                         sc.add("{");
535                         sc.indent();
536                         writeNewSetLocation(field, objectName, LOCATION_VAR + "s", sc);
537                         sc.unindent();
538                         sc.add("}");
539                     }
540 
541                     sc.add("while ( JsonToken.END_ARRAY != parser.nextToken() )");
542 
543                     sc.add("{");
544                     sc.indent();
545 
546                     if (inModel) {
547                         sc.add(adder + "( parse" + association.getTo() + "( parser, strict" + trackingArgs + " ) );");
548                     } else {
549                         String key;
550                         if (ModelDefault.SET.equals(type)) {
551                             key = "?";
552                         } else {
553                             key = "Integer.valueOf(" + associationName + ".size())";
554                         }
555                         writePrimitiveField(
556                                 association,
557                                 association.getTo(),
558                                 associationName,
559                                 LOCATION_VAR + "s",
560                                 key,
561                                 "add",
562                                 sc,
563                                 true);
564                     }
565 
566                     sc.unindent();
567                     sc.add("}");
568 
569                     sc.unindent();
570                     sc.add("}");
571                 } else {
572                     // Map or Properties
573 
574                     sc.add(tagComparison);
575 
576                     sc.add("{");
577                     sc.indent();
578 
579                     if (locationTracker != null) {
580                         sc.add(locationTracker.getName() + " " + LOCATION_VAR + "s;");
581                         writeNewSetLocation(field, objectName, LOCATION_VAR + "s", sc);
582                     }
583 
584                     if (xmlAssociationMetadata.isMapExplode()) {
585                         sc.add("if ( JsonToken.START_ARRAY != parser.nextToken() )");
586                         sc.add("{");
587                         sc.addIndented("throw new JsonParseException( \"Expected '"
588                                 + fieldTagName
589                                 + "' data to start with an Array\", parser.getCurrentLocation() );");
590                         sc.add("}");
591 
592                         sc.add("// " + xmlAssociationMetadata.getMapStyle() + " mode.");
593 
594                         sc.add("while ( JsonToken.END_ARRAY != parser.nextToken() )");
595 
596                         sc.add("{");
597                         sc.indent();
598 
599                         sc.add(
600                                 "if ( JsonToken.START_OBJECT != parser.getCurrentToken() && JsonToken.START_OBJECT != parser.nextToken() )");
601                         sc.add("{");
602                         sc.addIndented("throw new JsonParseException( \"Expected '"
603                                 + fieldTagName
604                                 + "' item data to start with an Object\", parser.getCurrentLocation() );");
605                         sc.add("}");
606 
607                         sc.add("String key = null;");
608 
609                         sc.add("String value = null;");
610 
611                         sc.add("Set<String> parsedPropertiesElements = new HashSet<String>();");
612 
613                         sc.add("while ( JsonToken.END_OBJECT != parser.nextToken() )");
614 
615                         sc.add("{");
616                         sc.indent();
617 
618                         sc.add("if ( checkFieldWithDuplicate( parser, \"key\", \"\", parsedPropertiesElements ) )");
619 
620                         sc.add("{");
621                         sc.addIndented("parser.nextToken();");
622 
623                         String parserGetter = "parser.getText()";
624 
625                         if (xmlFieldMetadata.isTrim()) {
626                             parserGetter = "getTrimmedValue( " + parserGetter + " )";
627                         }
628 
629                         sc.addIndented("key = " + parserGetter + ";");
630                         sc.add("}");
631 
632                         sc.add(
633                                 "else if ( checkFieldWithDuplicate( parser, \"value\", \"\", parsedPropertiesElements ) )");
634 
635                         sc.add("{");
636                         sc.addIndented("parser.nextToken();");
637 
638                         parserGetter = "parser.getText()";
639 
640                         if (xmlFieldMetadata.isTrim()) {
641                             parserGetter = "getTrimmedValue( " + parserGetter + " )";
642                         }
643 
644                         sc.addIndented("value = " + parserGetter + ";");
645                         sc.add("}");
646 
647                         sc.add("else");
648 
649                         sc.add("{");
650                         sc.addIndented("checkUnknownElement( parser, strict );");
651                         sc.add("}");
652 
653                         sc.unindent();
654                         sc.add("}");
655 
656                         sc.add(objectName + ".add" + capitalise(singularName) + "( key, value );");
657 
658                         sc.unindent();
659                         sc.add("}");
660                     } else {
661                         // INLINE Mode
662 
663                         sc.add("if ( JsonToken.START_OBJECT != parser.nextToken() )");
664                         sc.add("{");
665                         sc.addIndented("throw new JsonParseException( \"Expected '"
666                                 + fieldTagName
667                                 + "' data to start with an Object\", parser.getCurrentLocation() );");
668                         sc.add("}");
669 
670                         sc.add("while ( JsonToken.END_OBJECT != parser.nextToken() )");
671 
672                         sc.add("{");
673                         sc.indent();
674 
675                         sc.add("String key = parser.getCurrentName();");
676 
677                         writeNewSetLocation("key", LOCATION_VAR + "s", null, sc);
678 
679                         sc.add("String value = parser.nextTextValue()" + (xmlFieldMetadata.isTrim() ? ".trim()" : "")
680                                 + ";");
681 
682                         sc.add(objectName + ".add" + capitalise(singularName) + "( key, value );");
683 
684                         sc.unindent();
685                         sc.add("}");
686                     }
687 
688                     sc.unindent();
689                     sc.add("}");
690                 }
691             }
692         }
693     }
694 
695     private void writePrimitiveField(
696             ModelField field,
697             String type,
698             String objectName,
699             String locatorName,
700             String locationKey,
701             String setterName,
702             JSourceCode sc,
703             boolean wrappedItem) {
704         XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
705 
706         String parserGetter = null;
707         if ("boolean".equals(type) || "Boolean".equals(type)) {
708             parserGetter = "parser.getBooleanValue()";
709         } else if ("int".equals(type) || "Integer".equals(type)) {
710             parserGetter = "parser.getIntValue()";
711         } else if ("short".equals(type) || "Short".equals(type)) {
712             parserGetter = "parser.getShortValue()";
713         } else if ("long".equals(type) || "Long".equals(type)) {
714             parserGetter = "parser.getLongValue()";
715         } else if ("double".equals(type) || "Double".equals(type)) {
716             parserGetter = "parser.getDoubleValue()";
717         } else if ("float".equals(type) || "Float".equals(type)) {
718             parserGetter = "parser.getFloatValue()";
719         } else if ("byte".equals(type)) {
720             parserGetter = "parser.getByteValue()";
721         } else if ("String".equals(type)) {
722             parserGetter = "parser.getText()";
723 
724             if (xmlFieldMetadata.isTrim()) {
725                 parserGetter = "getTrimmedValue( " + parserGetter + " )";
726             }
727         } else if ("DOM".equals(type)) {
728             requiresDomSupport = true;
729             parserGetter = "parser.readValueAsTree()";
730         } else {
731             throw new IllegalArgumentException("Unknown type "
732                     + type
733                     + " for field "
734                     + field.getModelClass().getName()
735                     + "."
736                     + field.getName());
737         }
738 
739         String keyCapture = "";
740         writeNewLocation(null, sc);
741         if (locationTracker != null && "?".equals(locationKey)) {
742             sc.add("Object _key;");
743             locationKey = "_key";
744             keyCapture = "_key = ";
745         } else {
746             writeSetLocation(locationKey, locatorName, null, sc);
747         }
748 
749         // primitives token already consumed when in ARRAY loop
750         if (!wrappedItem) {
751             sc.add("parser.nextToken();");
752         }
753 
754         sc.add(objectName + "." + setterName + "( " + keyCapture + parserGetter + " );");
755 
756         if (!keyCapture.isEmpty()) {
757             writeSetLocation(locationKey, locatorName, null, sc);
758         }
759     }
760 
761     private void writeHelpers(JClass jClass) {
762         JMethod method = new JMethod("getTrimmedValue", new JClass("String"), null);
763         method.getModifiers().makePrivate();
764 
765         method.addParameter(new JParameter(new JClass("String"), "s"));
766 
767         JSourceCode sc = method.getSourceCode();
768 
769         sc.add("if ( s != null )");
770 
771         sc.add("{");
772         sc.addIndented("s = s.trim();");
773         sc.add("}");
774 
775         sc.add("return s;");
776 
777         jClass.addMethod(method);
778 
779         // --------------------------------------------------------------------
780 
781         method = new JMethod("getRequiredAttributeValue", new JClass("String"), null);
782         method.addException(new JClass("JsonParseException"));
783         method.getModifiers().makePrivate();
784 
785         method.addParameter(new JParameter(new JClass("String"), "s"));
786         method.addParameter(new JParameter(new JClass("String"), "attribute"));
787         method.addParameter(new JParameter(new JClass("JsonParser"), "parser"));
788         method.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
789 
790         sc = method.getSourceCode();
791 
792         sc.add("if ( s == null )");
793 
794         sc.add("{");
795         sc.indent();
796 
797         sc.add("if ( strict )");
798 
799         sc.add("{");
800         sc.addIndented(
801                 "throw new JsonParseException( \"Missing required value for attribute '\" + attribute + \"'\", parser.getCurrentLocation() );");
802         sc.add("}");
803 
804         sc.unindent();
805         sc.add("}");
806 
807         sc.add("return s;");
808 
809         jClass.addMethod(method);
810 
811         // --------------------------------------------------------------------
812 
813         method = new JMethod("checkFieldWithDuplicate", JType.BOOLEAN, null);
814         method.getModifiers().makePrivate();
815 
816         method.addParameter(new JParameter(new JClass("JsonParser"), "parser"));
817         method.addParameter(new JParameter(new JClass("String"), "tagName"));
818         method.addParameter(new JParameter(new JClass("String"), "alias"));
819         method.addParameter(new JParameter(new JClass("Set"), "parsed"));
820         method.addException(new JClass("IOException"));
821 
822         sc = method.getSourceCode();
823 
824         sc.add("String currentName = parser.getCurrentName();");
825 
826         sc.add("");
827 
828         sc.add("if ( !( currentName.equals( tagName ) || currentName.equals( alias ) ) )");
829 
830         sc.add("{");
831         sc.addIndented("return false;");
832         sc.add("}");
833 
834         sc.add("if ( !parsed.add( tagName ) )");
835 
836         sc.add("{");
837         sc.addIndented(
838                 "throw new JsonParseException( \"Duplicated tag: '\" + tagName + \"'\", parser.getCurrentLocation() );");
839         sc.add("}");
840 
841         sc.add("return true;");
842 
843         jClass.addMethod(method);
844 
845         // --------------------------------------------------------------------
846 
847         method = new JMethod("checkUnknownElement", null, null);
848         method.getModifiers().makePrivate();
849 
850         method.addParameter(new JParameter(new JClass("JsonParser"), "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 JsonParseException( \"Unrecognised tag: '\" + parser.getCurrentName() + \"'\", parser.getCurrentLocation() );");
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("JsonToken eventType = parser.nextToken();");
869         sc.add("if ( eventType == JsonToken.START_OBJECT )");
870         sc.add("{");
871         sc.addIndented("unrecognizedTagCount++;");
872         sc.add("}");
873         sc.add("else if ( eventType == JsonToken.END_OBJECT )");
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("JsonParser"), "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 JsonParseException( \"Unknown attribute '\" + attribute + \"' for tag '\" + tagName + \"'\", parser.getCurrentLocation() );");
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     private void addTrackingParameters(JMethod method) {
913         if (sourceTracker != null) {
914             method.addParameter(new JParameter(new JClass(sourceTracker.getName()), SOURCE_PARAM));
915         }
916     }
917 
918     private void writeNewSetLocation(ModelField field, String objectName, String trackerVariable, JSourceCode sc) {
919         writeNewSetLocation("\"" + field.getName() + "\"", objectName, trackerVariable, sc);
920     }
921 
922     private void writeNewSetLocation(String key, String objectName, String trackerVariable, JSourceCode sc) {
923         writeNewLocation(trackerVariable, sc);
924         writeSetLocation(key, objectName, trackerVariable, sc);
925     }
926 
927     private void writeNewLocation(String trackerVariable, JSourceCode sc) {
928         if (locationTracker == null) {
929             return;
930         }
931 
932         String constr = "new " + locationTracker.getName() + "( parser.getLineNumber(), parser.getColumnNumber()";
933         constr += (sourceTracker != null) ? ", " + SOURCE_PARAM : "";
934         constr += " )";
935 
936         sc.add(((trackerVariable != null) ? trackerVariable : LOCATION_VAR) + " = " + constr + ";");
937     }
938 
939     private void writeSetLocation(String key, String objectName, String trackerVariable, JSourceCode sc) {
940         if (locationTracker == null) {
941             return;
942         }
943 
944         String variable = (trackerVariable != null) ? trackerVariable : LOCATION_VAR;
945 
946         sc.add(objectName + ".set" + capitalise(singular(locationField)) + "( " + key + ", " + variable + " );");
947     }
948 }