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