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.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.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      public void generate(Model model, Properties parameters) throws ModelloException {
78          initialize(model, parameters);
79  
80          requiresDomSupport = false;
81          locationTracker = sourceTracker = null;
82          trackingArgs = locationField = "";
83  
84          if (isLocationTracking()) {
85              locationTracker = model.getLocationTracker(getGeneratedVersion());
86              if (locationTracker == null) {
87                  throw new ModelloException("No model class has been marked as location tracker"
88                          + " via the attribute locationTracker=\"locations\""
89                          + ", cannot generate extended reader.");
90              }
91  
92              locationField =
93                      ((ModelClassMetadata) locationTracker.getMetadata(ModelClassMetadata.ID)).getLocationTracker();
94  
95              sourceTracker = model.getSourceTracker(getGeneratedVersion());
96  
97              if (sourceTracker != null) {
98                  trackingArgs += ", " + SOURCE_PARAM;
99              }
100         }
101 
102         try {
103             generateJacksonReader();
104         } catch (IOException ex) {
105             throw new ModelloException("Exception while generating Jackson Reader.", ex);
106         }
107     }
108 
109     private void writeAllClassesReaders(Model objectModel, JClass jClass) {
110         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
111 
112         for (ModelClass clazz : getClasses(objectModel)) {
113             if (isTrackingSupport(clazz)) {
114                 continue;
115             }
116 
117             writeClassReaders(clazz, jClass, root.getName().equals(clazz.getName()));
118         }
119     }
120 
121     private void writeClassReaders(ModelClass modelClass, JClass jClass, boolean rootElement) {
122         JavaClassMetadata javaClassMetadata =
123                 (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.class.getName());
124 
125         // Skip abstract classes, no way to parse them out into objects
126         if (javaClassMetadata.isAbstract()) {
127             return;
128         }
129 
130         XmlClassMetadata xmlClassMetadata = (XmlClassMetadata) modelClass.getMetadata(XmlClassMetadata.ID);
131         if (!rootElement && !xmlClassMetadata.isStandaloneRead()) {
132             return;
133         }
134 
135         String className = modelClass.getName();
136 
137         String capClassName = capitalise(className);
138 
139         String readerMethodName = "read";
140         if (!rootElement) {
141             readerMethodName += capClassName;
142         }
143 
144         // ----------------------------------------------------------------------
145         // Write the read(JsonParser) method which will do the unmarshalling.
146         // ----------------------------------------------------------------------
147 
148         JMethod unmarshall = new JMethod(readerMethodName, new JClass(className), null);
149         unmarshall.getModifiers().makePrivate();
150 
151         unmarshall.addParameter(new JParameter(new JClass("JsonParser"), "parser"));
152         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
153         addTrackingParameters(unmarshall);
154 
155         unmarshall.addException(new JClass("IOException"));
156 
157         JSourceCode sc = unmarshall.getSourceCode();
158 
159         String variableName = uncapitalise(className);
160 
161         sc.add(className + ' ' + variableName + " = parse" + capClassName + "( parser, strict" + trackingArgs + " );");
162 
163         if (rootElement) {
164             // TODO
165             // sc.add( variableName + ".setModelEncoding( parser.getInputEncoding() );" );
166         }
167 
168         sc.add("return " + variableName + ';');
169 
170         jClass.addMethod(unmarshall);
171 
172         // ----------------------------------------------------------------------
173         // Write the read(Reader[,boolean]) methods which will do the unmarshalling.
174         // ----------------------------------------------------------------------
175 
176         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
177 
178         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
179         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
180         addTrackingParameters(unmarshall);
181 
182         unmarshall.addException(new JClass("IOException"));
183 
184         sc = unmarshall.getSourceCode();
185 
186         sc.add("JsonParser parser = factory.createParser( reader );");
187 
188         sc.add("return " + readerMethodName + "( parser, strict );");
189 
190         jClass.addMethod(unmarshall);
191         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
192 
193         unmarshall.addParameter(new JParameter(new JClass("Reader"), "reader"));
194 
195         unmarshall.addException(new JClass("IOException"));
196 
197         sc = unmarshall.getSourceCode();
198         sc.add("return " + readerMethodName + "( reader, true );");
199 
200         jClass.addMethod(unmarshall);
201 
202         // ----------------------------------------------------------------------
203         // Write the read(InputStream[,boolean]) methods which will do the unmarshalling.
204         // ----------------------------------------------------------------------
205 
206         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
207 
208         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "in"));
209         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
210         addTrackingParameters(unmarshall);
211 
212         unmarshall.addException(new JClass("IOException"));
213 
214         sc = unmarshall.getSourceCode();
215 
216         sc.add("return " + readerMethodName + "( new InputStreamReader( in ), strict" + trackingArgs + " );");
217 
218         jClass.addMethod(unmarshall);
219         unmarshall = new JMethod(readerMethodName, new JClass(className), null);
220 
221         unmarshall.addParameter(new JParameter(new JClass("InputStream"), "in"));
222 
223         unmarshall.addException(new JClass("IOException"));
224 
225         sc = unmarshall.getSourceCode();
226 
227         sc.add("return " + readerMethodName + "( in, true );");
228 
229         jClass.addMethod(unmarshall);
230 
231         // --------------------------------------------------------------------
232     }
233 
234     private void generateJacksonReader() throws ModelloException, IOException {
235         Model objectModel = getModel();
236 
237         String packageName =
238                 objectModel.getDefaultPackageName(isPackageWithVersion(), getGeneratedVersion()) + ".io.jackson";
239 
240         String unmarshallerName = getFileName("JacksonReader" + (isLocationTracking() ? "Ex" : ""));
241 
242         JSourceWriter sourceWriter = newJSourceWriter(packageName, unmarshallerName);
243 
244         JClass jClass = new JClass(packageName + '.' + unmarshallerName);
245         initHeader(jClass);
246         suppressAllWarnings(objectModel, jClass);
247 
248         jClass.addImport("com.fasterxml.jackson.core.JsonFactory");
249         jClass.addImport("com.fasterxml.jackson.core.JsonParser");
250         jClass.addImport("com.fasterxml.jackson.core.JsonParser.Feature");
251         jClass.addImport("com.fasterxml.jackson.core.JsonParseException");
252         jClass.addImport("com.fasterxml.jackson.core.JsonToken");
253         jClass.addImport("java.io.InputStream");
254         jClass.addImport("java.io.InputStreamReader");
255         jClass.addImport("java.io.IOException");
256         jClass.addImport("java.io.Reader");
257         jClass.addImport("java.text.DateFormat");
258         jClass.addImport("java.util.Set");
259         jClass.addImport("java.util.HashSet");
260 
261         addModelImports(jClass, null);
262 
263         JField factoryField = new JField(new JClass("JsonFactory"), "factory");
264         factoryField.getModifiers().setFinal(true);
265         factoryField.setInitString("new JsonFactory()");
266         jClass.addField(factoryField);
267 
268         JConstructor jacksonReaderConstructor = new JConstructor(jClass);
269         JSourceCode sc = jacksonReaderConstructor.getSourceCode();
270         sc.add("factory.enable( Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER  );");
271         sc.add("factory.enable( Feature.ALLOW_COMMENTS );");
272         sc.add("factory.enable( Feature.ALLOW_NON_NUMERIC_NUMBERS );");
273         sc.add("factory.enable( Feature.ALLOW_NUMERIC_LEADING_ZEROS );");
274         sc.add("factory.enable( Feature.ALLOW_SINGLE_QUOTES );");
275         sc.add("factory.enable( Feature.ALLOW_UNQUOTED_CONTROL_CHARS );");
276         sc.add("factory.enable( Feature.ALLOW_UNQUOTED_FIELD_NAMES );");
277 
278         jClass.addConstructor(jacksonReaderConstructor);
279 
280         // ----------------------------------------------------------------------
281         // Write the class parsers
282         // ----------------------------------------------------------------------
283 
284         writeAllClassesParser(objectModel, jClass);
285 
286         // ----------------------------------------------------------------------
287         // Write the class readers
288         // ----------------------------------------------------------------------
289 
290         writeAllClassesReaders(objectModel, jClass);
291 
292         // ----------------------------------------------------------------------
293         // Write helpers
294         // ----------------------------------------------------------------------
295 
296         writeHelpers(jClass);
297 
298         // ----------------------------------------------------------------------
299         // DOM support
300         // ----------------------------------------------------------------------
301 
302         if (requiresDomSupport) {
303             jClass.addImport("com.fasterxml.jackson.databind.ObjectMapper");
304 
305             sc.add("factory.setCodec( new ObjectMapper() );");
306         }
307 
308         jClass.print(sourceWriter);
309 
310         sourceWriter.close();
311     }
312 
313     private void writeAllClassesParser(Model objectModel, JClass jClass) {
314         ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
315 
316         for (ModelClass clazz : getClasses(objectModel)) {
317             if (isTrackingSupport(clazz)) {
318                 continue;
319             }
320 
321             writeClassParser(clazz, jClass, root.getName().equals(clazz.getName()));
322         }
323     }
324 
325     private void writeClassParser(ModelClass modelClass, JClass jClass, boolean rootElement) {
326         JavaClassMetadata javaClassMetadata =
327                 (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.class.getName());
328 
329         // Skip abstract classes, no way to parse them out into objects
330         if (javaClassMetadata.isAbstract()) {
331             return;
332         }
333 
334         String className = modelClass.getName();
335 
336         String capClassName = capitalise(className);
337 
338         String uncapClassName = uncapitalise(className);
339 
340         JMethod unmarshall = new JMethod("parse" + capClassName, new JClass(className), null);
341         unmarshall.getModifiers().makePrivate();
342 
343         unmarshall.addParameter(new JParameter(new JClass("JsonParser"), "parser"));
344         unmarshall.addParameter(new JParameter(JClass.BOOLEAN, "strict"));
345         addTrackingParameters(unmarshall);
346 
347         unmarshall.addException(new JClass("IOException"));
348 
349         JSourceCode sc = unmarshall.getSourceCode();
350 
351         sc.add(
352                 "if ( JsonToken.START_OBJECT != parser.getCurrentToken() && JsonToken.START_OBJECT != parser.nextToken() )");
353         sc.add("{");
354         sc.addIndented("throw new JsonParseException( \"Expected '"
355                 + className
356                 + "' data to start with an Object\", parser.getCurrentLocation() );");
357         sc.add("}");
358 
359         sc.add(className + " " + uncapClassName + " = new " + className + "();");
360 
361         if (locationTracker != null) {
362             sc.add(locationTracker.getName() + " " + LOCATION_VAR + ";");
363             writeNewSetLocation("\"\"", uncapClassName, null, sc);
364         }
365 
366         List<ModelField> modelFields = getFieldsForXml(modelClass, getGeneratedVersion());
367 
368         {
369             // Write other fields
370 
371             sc.add("Set<String> parsed = new HashSet<String>();");
372 
373             sc.add("while ( JsonToken.END_OBJECT != parser.nextToken() )");
374 
375             sc.add("{");
376             sc.indent();
377 
378             boolean addElse = false;
379 
380             for (ModelField field : modelFields) {
381                 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
382 
383                 processField(field, xmlFieldMetadata, addElse, sc, uncapClassName, jClass);
384 
385                 addElse = true;
386             }
387 
388             if (addElse) {
389                 sc.add("else");
390 
391                 sc.add("{");
392                 sc.indent();
393             }
394 
395             sc.add("checkUnknownElement( parser, strict );");
396 
397             if (addElse) {
398                 sc.unindent();
399                 sc.add("}");
400             }
401 
402             sc.unindent();
403             sc.add("}");
404         }
405 
406         sc.add("return " + uncapClassName + ";");
407 
408         jClass.addMethod(unmarshall);
409     }
410 
411     /**
412      * Generate code to process a field represented as an XML element.
413      *
414      * @param field            the field to process
415      * @param xmlFieldMetadata its XML metadata
416      * @param addElse          add an <code>else</code> statement before generating a new <code>if</code>
417      * @param sc               the method source code to add to
418      * @param objectName       the object name in the source
419      * @param jClass           the generated class source file
420      */
421     private void processField(
422             ModelField field,
423             XmlFieldMetadata xmlFieldMetadata,
424             boolean addElse,
425             JSourceCode sc,
426             String objectName,
427             JClass jClass) {
428         String fieldTagName = resolveTagName(field, xmlFieldMetadata);
429 
430         String capFieldName = capitalise(field.getName());
431 
432         String singularName = singular(field.getName());
433 
434         String alias;
435         if (StringUtils.isEmpty(field.getAlias())) {
436             alias = "null";
437         } else {
438             alias = "\"" + field.getAlias() + "\"";
439         }
440 
441         String tagComparison = (addElse ? "else " : "") + "if ( checkFieldWithDuplicate( parser, \"" + fieldTagName
442                 + "\", " + alias + ", parsed ) )";
443 
444         if (!(field instanceof ModelAssociation)) { // model field
445             sc.add(tagComparison);
446 
447             sc.add("{");
448 
449             sc.indent();
450 
451             writePrimitiveField(
452                     field,
453                     field.getType(),
454                     objectName,
455                     objectName,
456                     "\"" + field.getName() + "\"",
457                     "set" + capFieldName,
458                     sc,
459                     false);
460 
461             sc.unindent();
462             sc.add("}");
463         } else { // model association
464             ModelAssociation association = (ModelAssociation) field;
465 
466             String associationName = association.getName();
467 
468             if (association.isOneMultiplicity()) {
469                 sc.add(tagComparison);
470 
471                 sc.add("{");
472                 sc.addIndented(objectName + ".set" + capFieldName + "( parse" + association.getTo() + "( parser, strict"
473                         + trackingArgs + " ) );");
474                 sc.add("}");
475             } else {
476                 // MANY_MULTIPLICITY
477 
478                 XmlAssociationMetadata xmlAssociationMetadata =
479                         (XmlAssociationMetadata) association.getAssociationMetadata(XmlAssociationMetadata.ID);
480 
481                 String type = association.getType();
482 
483                 if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
484                     boolean inModel = isClassInModel(
485                             association.getTo(), field.getModelClass().getModel());
486 
487                     sc.add((addElse ? "else " : "") + "if ( checkFieldWithDuplicate( parser, \""
488                             + fieldTagName
489                             + "\", "
490                             + alias
491                             + ", parsed ) )");
492 
493                     sc.add("{");
494                     sc.indent();
495 
496                     sc.add("if ( JsonToken.START_ARRAY != parser.nextToken() )");
497                     sc.add("{");
498                     sc.addIndented("throw new JsonParseException( \"Expected '"
499                             + fieldTagName
500                             + "' data to start with an Array\", parser.getCurrentLocation() );");
501                     sc.add("}");
502 
503                     JavaFieldMetadata javaFieldMetadata =
504                             (JavaFieldMetadata) association.getMetadata(JavaFieldMetadata.ID);
505 
506                     String adder;
507 
508                     if (javaFieldMetadata.isGetter() && javaFieldMetadata.isSetter()) {
509                         sc.add(type + " " + associationName + " = " + objectName + ".get" + capFieldName + "();");
510 
511                         sc.add("if ( " + associationName + " == null )");
512 
513                         sc.add("{");
514                         sc.indent();
515 
516                         sc.add(associationName + " = " + association.getDefaultValue() + ";");
517 
518                         sc.add(objectName + ".set" + capFieldName + "( " + associationName + " );");
519 
520                         sc.unindent();
521                         sc.add("}");
522 
523                         adder = associationName + ".add";
524                     } else {
525                         adder = objectName + ".add" + association.getTo();
526                     }
527 
528                     if (!inModel && locationTracker != null) {
529                         sc.add(locationTracker.getName() + " " + LOCATION_VAR + "s = " + objectName + ".get"
530                                 + capitalise(singular(locationField)) + "( \"" + field.getName()
531                                 + "\" );");
532                         sc.add("if ( " + LOCATION_VAR + "s == null )");
533                         sc.add("{");
534                         sc.indent();
535                         writeNewSetLocation(field, objectName, LOCATION_VAR + "s", sc);
536                         sc.unindent();
537                         sc.add("}");
538                     }
539 
540                     sc.add("while ( JsonToken.END_ARRAY != parser.nextToken() )");
541 
542                     sc.add("{");
543                     sc.indent();
544 
545                     if (inModel) {
546                         sc.add(adder + "( parse" + association.getTo() + "( parser, strict" + trackingArgs + " ) );");
547                     } else {
548                         String key;
549                         if (ModelDefault.SET.equals(type)) {
550                             key = "?";
551                         } else {
552                             key = (hasJavaSourceSupport(5) ? "Integer.valueOf" : "new java.lang.Integer") + "( "
553                                     + 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.length() > 0) {
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 }