View Javadoc

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