View Javadoc

1   package org.codehaus.modello.plugin.java;
2   
3   /*
4    * Copyright (c) 2004, 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.io.Serializable;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Properties;
33  import java.util.Set;
34  
35  import org.codehaus.modello.ModelloException;
36  import org.codehaus.modello.ModelloRuntimeException;
37  import org.codehaus.modello.model.CodeSegment;
38  import org.codehaus.modello.model.Model;
39  import org.codehaus.modello.model.ModelAssociation;
40  import org.codehaus.modello.model.ModelClass;
41  import org.codehaus.modello.model.ModelDefault;
42  import org.codehaus.modello.model.ModelField;
43  import org.codehaus.modello.model.ModelInterface;
44  import org.codehaus.modello.plugin.java.javasource.JArrayType;
45  import org.codehaus.modello.plugin.java.javasource.JClass;
46  import org.codehaus.modello.plugin.java.javasource.JCollectionType;
47  import org.codehaus.modello.plugin.java.javasource.JConstructor;
48  import org.codehaus.modello.plugin.java.javasource.JDocDescriptor;
49  import org.codehaus.modello.plugin.java.javasource.JField;
50  import org.codehaus.modello.plugin.java.javasource.JInterface;
51  import org.codehaus.modello.plugin.java.javasource.JMapType;
52  import org.codehaus.modello.plugin.java.javasource.JMethod;
53  import org.codehaus.modello.plugin.java.javasource.JMethodSignature;
54  import org.codehaus.modello.plugin.java.javasource.JParameter;
55  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
56  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
57  import org.codehaus.modello.plugin.java.javasource.JType;
58  import org.codehaus.modello.plugin.java.metadata.JavaAssociationMetadata;
59  import org.codehaus.modello.plugin.java.metadata.JavaClassMetadata;
60  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
61  import org.codehaus.modello.plugin.model.ModelClassMetadata;
62  import org.codehaus.plexus.util.StringUtils;
63  
64  /**
65   * @author <a href="mailto:jason@modello.org">Jason van Zyl</a>
66   */
67  public class JavaModelloGenerator
68      extends AbstractJavaModelloGenerator
69  {
70  
71      private Collection<String> immutableTypes = new HashSet<String>( Arrays.asList(
72          new String[]{ "boolean", "Boolean", "byte", "Byte", "char", "Character", "short", "Short", "int", "Integer",
73              "long", "Long", "float", "Float", "double", "Double", "String" } ) );
74  
75      public void generate( Model model, Properties parameters )
76          throws ModelloException
77      {
78          initialize( model, parameters );
79  
80          try
81          {
82              generateJava();
83          }
84          catch ( IOException ex )
85          {
86              throw new ModelloException( "Exception while generating Java.", ex );
87          }
88      }
89  
90      private void generateJava()
91          throws ModelloException, IOException
92      {
93          Model objectModel = getModel();
94  
95          ModelClass locationTrackerClass = objectModel.getLocationTracker( getGeneratedVersion() );
96          ModelClass sourceTrackerClass = objectModel.getSourceTracker( getGeneratedVersion() );
97  
98          // ----------------------------------------------------------------------
99          // Generate the interfaces.
100         // ----------------------------------------------------------------------
101 
102         for ( ModelInterface modelInterface : objectModel.getInterfaces( getGeneratedVersion() ) )
103         {
104             generateInterface( modelInterface );
105         }
106 
107         String locationTrackerInterface = generateLocationTracker( objectModel, locationTrackerClass );
108 
109         // ----------------------------------------------------------------------
110         // Generate the classes.
111         // ----------------------------------------------------------------------
112 
113         for ( ModelClass modelClass : objectModel.getClasses( getGeneratedVersion() ) )
114         {
115             JavaClassMetadata javaClassMetadata = (JavaClassMetadata) modelClass.getMetadata( JavaClassMetadata.ID );
116 
117             if ( !javaClassMetadata.isEnabled() )
118             {
119                 // Skip generation of those classes that are not enabled for the java plugin.
120                 continue;
121             }
122 
123             String packageName = modelClass.getPackageName( isPackageWithVersion(), getGeneratedVersion() );
124 
125             JSourceWriter sourceWriter = newJSourceWriter( packageName, modelClass.getName() );
126 
127             JClass jClass = new JClass( packageName + '.' + modelClass.getName() );
128 
129             initHeader( jClass );
130 
131             suppressAllWarnings( objectModel, jClass );
132 
133             if ( StringUtils.isNotEmpty( modelClass.getDescription() ) )
134             {
135                 jClass.getJDocComment().setComment( appendPeriod( modelClass.getDescription() ) );
136             }
137 
138             addModelImports( jClass, modelClass );
139 
140             jClass.getModifiers().setAbstract( javaClassMetadata.isAbstract() );
141 
142             boolean superClassInModel = false;
143             if ( modelClass.getSuperClass() != null )
144             {
145                 jClass.setSuperClass( modelClass.getSuperClass() );
146                 superClassInModel = isClassInModel( modelClass.getSuperClass(), objectModel );
147             }
148 
149             for ( String implementedInterface : modelClass.getInterfaces() )
150             {
151                 jClass.addInterface( implementedInterface );
152             }
153 
154             jClass.addInterface( Serializable.class.getName() );
155 
156             if ( useJava5 && !modelClass.getAnnotations().isEmpty() )
157             {
158                 for ( String annotation : modelClass.getAnnotations() )
159                 {
160                     jClass.appendAnnotation( annotation );
161                 }
162             }
163 
164             JSourceCode jConstructorSource = new JSourceCode();
165 
166             for ( ModelField modelField : modelClass.getFields( getGeneratedVersion() ) )
167             {
168                 if ( modelField instanceof ModelAssociation )
169                 {
170                     createAssociation( jClass, (ModelAssociation) modelField, jConstructorSource );
171                 }
172                 else
173                 {
174                     createField( jClass, modelField );
175                 }
176             }
177 
178             // since 1.8
179             // needed to understand if the instance can be created with empty ctor or not
180             JConstructor jConstructor = null;
181 
182             if ( !jConstructorSource.isEmpty() )
183             {
184                 // Ironic that we are doing lazy init huh?
185                 jConstructor = jClass.createConstructor();
186                 jConstructor.setSourceCode( jConstructorSource );
187                 jClass.addConstructor( jConstructor );
188             }
189 
190             // ----------------------------------------------------------------------
191             // equals() / hashCode() / toString()
192             // ----------------------------------------------------------------------
193 
194             List<ModelField> identifierFields = modelClass.getIdentifierFields( getGeneratedVersion() );
195 
196             if ( identifierFields.size() != 0 )
197             {
198                 JMethod equals = generateEquals( modelClass );
199 
200                 jClass.addMethod( equals );
201 
202                 JMethod hashCode = generateHashCode( modelClass );
203 
204                 jClass.addMethod( hashCode );
205 
206                 // backward compat
207                 if ( !javaClassMetadata.isGenerateToString() )
208                 {
209                     JMethod toString = generateToString( modelClass, true );
210 
211                     jClass.addMethod( toString );
212                 }
213 
214             }
215 
216             if ( javaClassMetadata.isGenerateToString() )
217             {
218 
219                 JMethod toString = generateToString( modelClass, false );
220 
221                 jClass.addMethod( toString );
222 
223             }
224 
225             // ----------------------------------------------------------------------
226             // Model.Builder
227             // since 1.8
228             // ----------------------------------------------------------------------
229 
230             if ( javaClassMetadata.isGenerateBuilder() )
231             {
232                 generateBuilder( modelClass, jClass.createInnerClass( "Builder" ), jConstructor );
233             }
234 
235             // ----------------------------------------------------------------------
236             // Model.newXXXInstance
237             // since 1.8
238             // ----------------------------------------------------------------------
239 
240             if ( javaClassMetadata.isGenerateStaticCreators() )
241             {
242                 generateStaticCreator( modelClass, jClass, jConstructor );
243             }
244 
245             boolean cloneLocations = !superClassInModel && modelClass != sourceTrackerClass;
246             JMethod[] cloneMethods = generateClone( modelClass, cloneLocations ? locationTrackerClass : null );
247             if ( cloneMethods.length > 0 )
248             {
249                 jClass.addInterface( Cloneable.class.getName() );
250                 jClass.addMethods( cloneMethods );
251             }
252 
253             if ( modelClass.getCodeSegments( getGeneratedVersion() ) != null )
254             {
255                 for ( CodeSegment codeSegment : modelClass.getCodeSegments( getGeneratedVersion() ) )
256                 {
257                     jClass.addSourceCode( codeSegment.getCode() );
258                 }
259             }
260 
261             ModelClassMetadata modelClassMetadata =
262                 (ModelClassMetadata) modelClass.getMetadata( ModelClassMetadata.ID );
263 
264             if ( modelClassMetadata != null )
265             {
266                 if ( modelClassMetadata.isRootElement() )
267                 {
268                     ModelField modelEncoding = new ModelField( modelClass, "modelEncoding" );
269                     modelEncoding.setType( "String" );
270                     modelEncoding.setDefaultValue( "UTF-8" );
271                     modelEncoding.addMetadata( new JavaFieldMetadata() );
272                     createField( jClass, modelEncoding );
273                 }
274             }
275 
276             if ( modelClass == locationTrackerClass )
277             {
278                 jClass.addInterface( locationTrackerInterface );
279 
280                 generateLocationBean( jClass, modelClass, sourceTrackerClass );
281 
282                 generateLocationTracking( jClass, modelClass, locationTrackerClass );
283             }
284             else if ( locationTrackerClass != null && modelClass != sourceTrackerClass && !superClassInModel )
285             {
286                 jClass.addInterface( locationTrackerInterface );
287 
288                 generateLocationTracking( jClass, modelClass, locationTrackerClass );
289             }
290 
291             jClass.print( sourceWriter );
292 
293             sourceWriter.close();
294         }
295     }
296 
297     private void generateInterface( ModelInterface modelInterface )
298         throws ModelloException, IOException
299     {
300         Model objectModel = modelInterface.getModel();
301 
302         String packageName = modelInterface.getPackageName( isPackageWithVersion(), getGeneratedVersion() );
303 
304         JSourceWriter sourceWriter = newJSourceWriter( packageName, modelInterface.getName() );
305 
306         JInterface jInterface = new JInterface( packageName + '.' + modelInterface.getName() );
307 
308         initHeader( jInterface );
309 
310         suppressAllWarnings( objectModel, jInterface );
311 
312         if ( modelInterface.getSuperInterface() != null )
313         {
314             // check if we need an import: if it is a generated superInterface in another package
315             try
316             {
317                 ModelInterface superInterface =
318                     objectModel.getInterface( modelInterface.getSuperInterface(), getGeneratedVersion() );
319                 String superPackageName =
320                     superInterface.getPackageName( isPackageWithVersion(), getGeneratedVersion() );
321 
322                 if ( !packageName.equals( superPackageName ) )
323                 {
324                     jInterface.addImport( superPackageName + '.' + superInterface.getName() );
325                 }
326             }
327             catch ( ModelloRuntimeException mre )
328             {
329                 // no problem if the interface does not exist in the model, it can be in the jdk
330             }
331 
332             jInterface.addInterface( modelInterface.getSuperInterface() );
333         }
334 
335         if ( modelInterface.getCodeSegments( getGeneratedVersion() ) != null )
336         {
337             for ( CodeSegment codeSegment : modelInterface.getCodeSegments( getGeneratedVersion() ) )
338             {
339                 jInterface.addSourceCode( codeSegment.getCode() );
340             }
341         }
342 
343         if ( useJava5 && !modelInterface.getAnnotations().isEmpty() )
344         {
345             for ( String annotation : modelInterface.getAnnotations() )
346             {
347                 jInterface.appendAnnotation( annotation );
348             }
349         }
350 
351         jInterface.print( sourceWriter );
352 
353         sourceWriter.close();
354     }
355 
356     private JMethod generateEquals( ModelClass modelClass )
357     {
358         JMethod equals = new JMethod( "equals", JType.BOOLEAN, null );
359 
360         equals.addParameter( new JParameter( new JClass( "Object" ), "other" ) );
361 
362         JSourceCode sc = equals.getSourceCode();
363 
364         sc.add( "if ( this == other )" );
365         sc.add( "{" );
366         sc.addIndented( "return true;" );
367         sc.add( "}" );
368         sc.add( "" );
369         sc.add( "if ( !( other instanceof " + modelClass.getName() + " ) )" );
370         sc.add( "{" );
371         sc.addIndented( "return false;" );
372         sc.add( "}" );
373         sc.add( "" );
374         sc.add( modelClass.getName() + " that = (" + modelClass.getName() + ") other;" );
375         sc.add( "boolean result = true;" );
376 
377         sc.add( "" );
378 
379         for ( ModelField identifier : modelClass.getIdentifierFields( getGeneratedVersion() ) )
380         {
381             String name = identifier.getName();
382             if ( "boolean".equals( identifier.getType() ) || "byte".equals( identifier.getType() ) || "char".equals(
383                 identifier.getType() ) || "double".equals( identifier.getType() ) || "float".equals(
384                 identifier.getType() ) || "int".equals( identifier.getType() ) || "short".equals( identifier.getType() )
385                 || "long".equals( identifier.getType() ) )
386             {
387                 sc.add( "result = result && " + name + " == that." + name + ";" );
388             }
389             else
390             {
391                 name = "get" + capitalise( name ) + "()";
392                 sc.add(
393                     "result = result && ( " + name + " == null ? that." + name + " == null : " + name + ".equals( that."
394                         + name + " ) );" );
395             }
396         }
397 
398         if ( modelClass.getSuperClass() != null )
399         {
400             sc.add( "result = result && ( super.equals( other ) );" );
401         }
402 
403         sc.add( "" );
404 
405         sc.add( "return result;" );
406 
407         return equals;
408     }
409 
410     private JMethod generateToString( ModelClass modelClass, boolean onlyIdentifierFields )
411     {
412         JMethod toString = new JMethod( "toString", new JType( String.class.getName() ), null );
413 
414         List<ModelField> fields = onlyIdentifierFields ? modelClass.getIdentifierFields( getGeneratedVersion() ) : modelClass.getFields( getGeneratedVersion() );
415 
416         JSourceCode sc = toString.getSourceCode();
417 
418         if ( fields.size() == 0 )
419         {
420             sc.add( "return super.toString();" );
421 
422             return toString;
423         }
424 
425         if ( useJava5 )
426         {
427             sc.add( "StringBuilder buf = new StringBuilder( 128 );" );
428         }
429         else
430         {
431             sc.add( "StringBuffer buf = new StringBuffer( 128 );" );
432         }
433 
434         sc.add( "" );
435 
436         for ( Iterator<ModelField> j = fields.iterator(); j.hasNext(); )
437         {
438             ModelField identifier = j.next();
439 
440             String getter = "boolean".equals( identifier.getType() ) ? "is" : "get";
441 
442             sc.add( "buf.append( \"" + identifier.getName() + " = '\" );" );
443             sc.add( "buf.append( " + getter + capitalise( identifier.getName() ) + "() );" );
444             sc.add( "buf.append( \"'\" );" );
445 
446             if ( j.hasNext() )
447             {
448                 sc.add( "buf.append( \"\\n\" ); " );
449             }
450         }
451 
452         if ( modelClass.getSuperClass() != null )
453         {
454             sc.add( "buf.append( \"\\n\" );" );
455             sc.add( "buf.append( super.toString() );" );
456         }
457 
458         sc.add( "" );
459 
460         sc.add( "return buf.toString();" );
461 
462         return toString;
463     }
464 
465     private JMethod generateHashCode( ModelClass modelClass )
466     {
467         JMethod hashCode = new JMethod( "hashCode", JType.INT, null );
468 
469         List<ModelField> identifierFields = modelClass.getIdentifierFields( getGeneratedVersion() );
470 
471         JSourceCode sc = hashCode.getSourceCode();
472 
473         if ( identifierFields.size() == 0 )
474         {
475             sc.add( "return super.hashCode();" );
476 
477             return hashCode;
478         }
479 
480         sc.add( "int result = 17;" );
481 
482         sc.add( "" );
483 
484         for ( ModelField identifier : identifierFields )
485         {
486             sc.add( "result = 37 * result + " + createHashCodeForField( identifier ) + ";" );
487         }
488 
489         if ( modelClass.getSuperClass() != null )
490         {
491             sc.add( "result = 37 * result + super.hashCode();" );
492         }
493 
494         sc.add( "" );
495 
496         sc.add( "return result;" );
497 
498         return hashCode;
499     }
500 
501     private JMethod[] generateClone( ModelClass modelClass, ModelClass locationClass )
502         throws ModelloException
503     {
504         String cloneModeClass = getCloneMode( modelClass );
505 
506         if ( JavaClassMetadata.CLONE_NONE.equals( cloneModeClass ) )
507         {
508             return new JMethod[0];
509         }
510 
511         JType returnType;
512         if ( useJava5 )
513         {
514             returnType = new JClass( modelClass.getName() );
515         }
516         else
517         {
518             returnType = new JClass( "Object" );
519         }
520 
521         JMethod cloneMethod = new JMethod( "clone", returnType, null );
522 
523         JSourceCode sc = cloneMethod.getSourceCode();
524 
525         sc.add( "try" );
526         sc.add( "{" );
527         sc.indent();
528 
529         sc.add( modelClass.getName() + " copy = (" + modelClass.getName() + ") super.clone();" );
530 
531         sc.add( "" );
532 
533         for ( ModelField modelField : modelClass.getFields( getGeneratedVersion() ) )
534         {
535             String thisField = "this." + modelField.getName();
536             String copyField = "copy." + modelField.getName();
537 
538             if ( "DOM".equals( modelField.getType() ) )
539             {
540                 sc.add( "if ( " + thisField + " != null )" );
541                 sc.add( "{" );
542                 if ( domAsXpp3 )
543                 {
544                     sc.addIndented( copyField
545                                         + " = new org.codehaus.plexus.util.xml.Xpp3Dom( (org.codehaus.plexus.util.xml.Xpp3Dom) "
546                                         + thisField + " );" );
547                 }
548                 else
549                 {
550                     sc.addIndented( copyField + " = ( (org.w3c.dom.Node) " + thisField + ").cloneNode( true );" );
551                 }
552                 sc.add( "}" );
553                 sc.add( "" );
554             }
555             else if ( "Date".equalsIgnoreCase( modelField.getType() ) || "java.util.Date".equals(
556                 modelField.getType() ) )
557             {
558                 sc.add( "if ( " + thisField + " != null )" );
559                 sc.add( "{" );
560                 sc.addIndented( copyField + " = (java.util.Date) " + thisField + ".clone();" );
561                 sc.add( "}" );
562                 sc.add( "" );
563             }
564             else if ( ModelDefault.PROPERTIES.equals( modelField.getType() ) )
565             {
566                 sc.add( "if ( " + thisField + " != null )" );
567                 sc.add( "{" );
568                 sc.addIndented( copyField + " = (" + ModelDefault.PROPERTIES + ") " + thisField + ".clone();" );
569                 sc.add( "}" );
570                 sc.add( "" );
571             }
572             else if ( modelField instanceof ModelAssociation )
573             {
574                 ModelAssociation modelAssociation = (ModelAssociation) modelField;
575 
576                 String cloneModeAssoc = getCloneMode( modelAssociation, cloneModeClass );
577 
578                 boolean deepClone =
579                     JavaAssociationMetadata.CLONE_DEEP.equals( cloneModeAssoc ) && !immutableTypes.contains(
580                         modelAssociation.getTo() );
581 
582                 if ( modelAssociation.isOneMultiplicity() )
583                 {
584                     if ( deepClone )
585                     {
586                         sc.add( "if ( " + thisField + " != null )" );
587                         sc.add( "{" );
588                         sc.addIndented(
589                             copyField + " = (" + modelAssociation.getTo() + ") " + thisField + ".clone();" );
590                         sc.add( "}" );
591                         sc.add( "" );
592                     }
593                 }
594                 else
595                 {
596                     sc.add( "if ( " + thisField + " != null )" );
597                     sc.add( "{" );
598                     sc.indent();
599 
600                     JavaAssociationMetadata javaAssociationMetadata = getJavaAssociationMetadata( modelAssociation );
601                     JType componentType = getComponentType( modelAssociation, javaAssociationMetadata );
602 
603                     sc.add( copyField + " = " + getDefaultValue( modelAssociation, componentType ) + ";" );
604 
605                     if ( isCollection( modelField.getType() ) )
606                     {
607                         if ( deepClone )
608                         {
609                             if ( useJava5 )
610                             {
611                                 sc.add( "for ( " + componentType.getName() + " item : " + thisField + " )" );
612                             }
613                             else
614                             {
615                                 sc.add( "for ( java.util.Iterator it = " + thisField + ".iterator(); it.hasNext(); )" );
616                             }
617                             sc.add( "{" );
618                             sc.indent();
619                             if ( useJava5 )
620                             {
621                                 sc.add( copyField + ".add( ( (" + modelAssociation.getTo() + ") item).clone() );" );
622                             }
623                             else
624                             {
625                                 sc.add(
626                                     copyField + ".add( ( (" + modelAssociation.getTo() + ") it.next() ).clone() );" );
627                             }
628                             sc.unindent();
629                             sc.add( "}" );
630                         }
631                         else
632                         {
633                             sc.add( copyField + ".addAll( " + thisField + " );" );
634                         }
635                     }
636                     else if ( isMap( modelField.getType() ) )
637                     {
638                         sc.add( copyField + ".clear();" );
639                         sc.add( copyField + ".putAll( " + thisField + " );" );
640                     }
641 
642                     sc.unindent();
643                     sc.add( "}" );
644                     sc.add( "" );
645                 }
646             }
647         }
648 
649         if ( locationClass != null )
650         {
651             String locationField =
652                 ( (ModelClassMetadata) locationClass.getMetadata( ModelClassMetadata.ID ) ).getLocationTracker();
653             sc.add( "if ( copy." + locationField + " != null )" );
654             sc.add( "{" );
655             sc.indent();
656             sc.add( "copy." + locationField + " = new java.util.LinkedHashMap" + "( copy." + locationField + " );" );
657             sc.unindent();
658             sc.add( "}" );
659             sc.add( "" );
660         }
661 
662         String cloneHook = getCloneHook( modelClass );
663 
664         if ( StringUtils.isNotEmpty( cloneHook ) && !"false".equalsIgnoreCase( cloneHook ) )
665         {
666             if ( "true".equalsIgnoreCase( cloneHook ) )
667             {
668                 cloneHook = "cloneHook";
669             }
670 
671             sc.add( cloneHook + "( copy );" );
672             sc.add( "" );
673         }
674 
675         sc.add( "return copy;" );
676 
677         sc.unindent();
678         sc.add( "}" );
679         sc.add( "catch ( " + Exception.class.getName() + " ex )" );
680         sc.add( "{" );
681         sc.indent();
682         sc.add( "throw (" + RuntimeException.class.getName() + ") new " + UnsupportedOperationException.class.getName()
683                     + "( getClass().getName()" );
684         sc.addIndented( "+ \" does not support clone()\" ).initCause( ex );" );
685         sc.unindent();
686         sc.add( "}" );
687 
688         return new JMethod[]{ cloneMethod };
689     }
690 
691     private String getCloneMode( ModelClass modelClass )
692         throws ModelloException
693     {
694         String cloneMode = null;
695 
696         for ( ModelClass currentClass = modelClass; ; )
697         {
698             JavaClassMetadata javaClassMetadata = (JavaClassMetadata) currentClass.getMetadata( JavaClassMetadata.ID );
699 
700             cloneMode = javaClassMetadata.getCloneMode();
701 
702             if ( cloneMode != null )
703             {
704                 break;
705             }
706 
707             String superClass = currentClass.getSuperClass();
708             if ( StringUtils.isEmpty( superClass ) || !isClassInModel( superClass, getModel() ) )
709             {
710                 break;
711             }
712 
713             currentClass = getModel().getClass( superClass, getGeneratedVersion() );
714         }
715 
716         if ( cloneMode == null )
717         {
718             cloneMode = JavaClassMetadata.CLONE_NONE;
719         }
720         else if ( !JavaClassMetadata.CLONE_MODES.contains( cloneMode ) )
721         {
722             throw new ModelloException(
723                 "The Java Modello Generator cannot use '" + cloneMode + "' as a value for <class java.clone=\"...\">, "
724                     + "only the following values are acceptable " + JavaClassMetadata.CLONE_MODES );
725         }
726 
727         return cloneMode;
728     }
729 
730     private String getCloneMode( ModelAssociation modelAssociation, String cloneModeClass )
731         throws ModelloException
732     {
733         JavaAssociationMetadata javaAssociationMetadata =
734             (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
735 
736         String cloneModeAssoc = javaAssociationMetadata.getCloneMode();
737         if ( cloneModeAssoc == null )
738         {
739             cloneModeAssoc = cloneModeClass;
740         }
741         else if ( !JavaAssociationMetadata.CLONE_MODES.contains( cloneModeAssoc ) )
742         {
743             throw new ModelloException( "The Java Modello Generator cannot use '" + cloneModeAssoc
744                                             + "' as a value for <association java.clone=\"...\">, "
745                                             + "only the following values are acceptable "
746                                             + JavaAssociationMetadata.CLONE_MODES );
747         }
748 
749         return cloneModeAssoc;
750     }
751 
752     private String getCloneHook( ModelClass modelClass )
753         throws ModelloException
754     {
755         JavaClassMetadata javaClassMetadata = (JavaClassMetadata) modelClass.getMetadata( JavaClassMetadata.ID );
756 
757         return javaClassMetadata.getCloneHook();
758     }
759 
760     private String generateLocationTracker( Model objectModel, ModelClass locationClass )
761         throws ModelloException, IOException
762     {
763         if ( locationClass == null )
764         {
765             return null;
766         }
767 
768         String locationField =
769             ( (ModelClassMetadata) locationClass.getMetadata( ModelClassMetadata.ID ) ).getLocationTracker();
770 
771         String propertyName = capitalise( singular( locationField ) );
772 
773         String interfaceName = locationClass.getName() + "Tracker";
774 
775         String packageName = locationClass.getPackageName( isPackageWithVersion(), getGeneratedVersion() );
776 
777         JSourceWriter sourceWriter = newJSourceWriter( packageName, interfaceName );
778 
779         JInterface jInterface = new JInterface( packageName + '.' + interfaceName );
780 
781         initHeader( jInterface );
782 
783         suppressAllWarnings( objectModel, jInterface );
784 
785         JMethodSignature jMethod = new JMethodSignature( "get" + propertyName, new JType( locationClass.getName() ) );
786         jMethod.setComment( "Gets the location of the specified field in the input source." );
787         addParameter( jMethod, "Object", "field", "The key of the field, must not be <code>null</code>." );
788         String returnDoc = "The location of the field in the input source or <code>null</code> if unknown.";
789         jMethod.getJDocComment().addDescriptor( JDocDescriptor.createReturnDesc( returnDoc ) );
790         jInterface.addMethod( jMethod );
791 
792         jMethod = new JMethodSignature( "set" + propertyName, null );
793         jMethod.setComment( "Sets the location of the specified field." );
794         addParameter( jMethod, "Object", "field", "The key of the field, must not be <code>null</code>." );
795         addParameter( jMethod, locationClass.getName(), singular( locationField ),
796                       "The location of the field, may be <code>null</code>." );
797         jInterface.addMethod( jMethod );
798 
799         jInterface.print( sourceWriter );
800 
801         sourceWriter.close();
802 
803         return jInterface.getName();
804     }
805 
806     private void generateLocationTracking( JClass jClass, ModelClass modelClass, ModelClass locationClass )
807         throws ModelloException
808     {
809         if ( locationClass == null )
810         {
811             return;
812         }
813 
814         String superClass = modelClass.getSuperClass();
815         if ( StringUtils.isNotEmpty( superClass ) && isClassInModel( superClass, getModel() ) )
816         {
817             return;
818         }
819 
820         ModelClassMetadata metadata = (ModelClassMetadata) locationClass.getMetadata( ModelClassMetadata.ID );
821         String locationField = metadata.getLocationTracker();
822 
823         String fieldType = "java.util.Map" + ( useJava5 ? "<Object, " + locationClass.getName() + ">" : "" );
824         String fieldImpl = "java.util.LinkedHashMap" + ( useJava5 ? "<Object, " + locationClass.getName() + ">" : "" );
825 
826         // private java.util.Map<Object, Location> locations;
827         JField jField = new JField( new JType( fieldType ), locationField );
828         jClass.addField( jField );
829 
830         JMethod jMethod;
831         JSourceCode sc;
832 
833         // public Location getLocation( Object key )
834         jMethod =
835             new JMethod( "get" + capitalise( singular( locationField ) ), new JType( locationClass.getName() ), null );
836         jMethod.addParameter( new JParameter( new JType( "Object" ), "key" ) );
837         sc = jMethod.getSourceCode();
838         sc.add( "return ( " + locationField + " != null ) ? " + locationField + ".get( key ) : null;" );
839         jMethod.setComment( "" );
840         jClass.addMethod( jMethod );
841 
842         // public void setLocation( Object key, Location location )
843         jMethod = new JMethod( "set" + capitalise( singular( locationField ) ) );
844         jMethod.addParameter( new JParameter( new JType( "Object" ), "key" ) );
845         jMethod.addParameter( new JParameter( new JType( locationClass.getName() ), singular( locationField ) ) );
846         sc = jMethod.getSourceCode();
847         sc.add( "if ( " + singular( locationField ) + " != null )" );
848         sc.add( "{" );
849         sc.indent();
850         sc.add( "if ( this." + locationField + " == null )" );
851         sc.add( "{" );
852         sc.addIndented( "this." + locationField + " = new " + fieldImpl + "();" );
853         sc.add( "}" );
854         sc.add( "this." + locationField + ".put( key, " + singular( locationField ) + " );" );
855         sc.unindent();
856         sc.add( "}" );
857         jMethod.setComment( "" );
858         jClass.addMethod( jMethod );
859     }
860 
861     private void generateLocationBean( JClass jClass, ModelClass locationClass, ModelClass sourceClass )
862         throws ModelloException
863     {
864         jClass.getModifiers().setFinal( true );
865 
866         String locationsField =
867             ( (ModelClassMetadata) locationClass.getMetadata( ModelClassMetadata.ID ) ).getLocationTracker();
868 
869         JavaFieldMetadata readOnlyField = new JavaFieldMetadata();
870         readOnlyField.setSetter( false );
871 
872         // int lineNumber;
873         ModelField lineNumber = new ModelField( locationClass, "lineNumber" );
874         lineNumber.setDescription( "The one-based line number. The value will be non-positive if unknown." );
875         lineNumber.setType( "int" );
876         lineNumber.setDefaultValue( "-1" );
877         lineNumber.addMetadata( readOnlyField );
878         createField( jClass, lineNumber );
879 
880         // int columnNumber;
881         ModelField columnNumber = new ModelField( locationClass, "columnNumber" );
882         columnNumber.setDescription( "The one-based column number. The value will be non-positive if unknown." );
883         columnNumber.setType( "int" );
884         columnNumber.setDefaultValue( "-1" );
885         columnNumber.addMetadata( readOnlyField );
886         createField( jClass, columnNumber );
887 
888         // Source source;
889         ModelField source = null;
890         if ( sourceClass != null )
891         {
892             ModelClassMetadata metadata = (ModelClassMetadata) sourceClass.getMetadata( ModelClassMetadata.ID );
893             String sourceField = metadata.getSourceTracker();
894 
895             source = new ModelField( locationClass, sourceField );
896             source.setType( sourceClass.getName() );
897             source.addMetadata( readOnlyField );
898             createField( jClass, source );
899         }
900 
901         // Location( int lineNumber, int columnNumber );
902         JConstructor jConstructor = jClass.createConstructor();
903         JSourceCode sc = jConstructor.getSourceCode();
904 
905         jConstructor.addParameter( new JParameter( JType.INT, lineNumber.getName() ) );
906         sc.add( "this." + lineNumber.getName() + " = " + lineNumber.getName() + ";" );
907 
908         jConstructor.addParameter( new JParameter( JType.INT, columnNumber.getName() ) );
909         sc.add( "this." + columnNumber.getName() + " = " + columnNumber.getName() + ";" );
910 
911         // Location( int lineNumber, int columnNumber, Source source );
912         if ( sourceClass != null )
913         {
914             jConstructor = jClass.createConstructor( jConstructor.getParameters() );
915             sc.copyInto( jConstructor.getSourceCode() );
916             sc = jConstructor.getSourceCode();
917 
918             jConstructor.addParameter( new JParameter( new JType( sourceClass.getName() ), source.getName() ) );
919             sc.add( "this." + source.getName() + " = " + source.getName() + ";" );
920         }
921 
922         String fieldType = "java.util.Map" + ( useJava5 ? "<Object, " + locationClass.getName() + ">" : "" );
923         String fieldImpl = "java.util.LinkedHashMap" + ( useJava5 ? "<Object, " + locationClass.getName() + ">" : "" );
924 
925         // public Map<Object, Location> getLocations()
926         JMethod jMethod = new JMethod( "get" + capitalise( locationsField ), new JType( fieldType ), null );
927         sc = jMethod.getSourceCode();
928         sc.add( "return " + locationsField + ";" );
929         jMethod.setComment( "" );
930         jClass.addMethod( jMethod );
931 
932         // public void setLocations( Map<Object, Location> locations )
933         jMethod = new JMethod( "set" + capitalise( locationsField ) );
934         jMethod.addParameter( new JParameter( new JType( fieldType ), locationsField ) );
935         sc = jMethod.getSourceCode();
936         sc.add( "this." + locationsField + " = " + locationsField + ";" );
937         jMethod.setComment( "" );
938         jClass.addMethod( jMethod );
939 
940         // public static Location merge( Location target, Location source, boolean sourceDominant )
941         jMethod = new JMethod( "merge", new JType( locationClass.getName() ), null );
942         jMethod.getModifiers().setStatic( true );
943         jMethod.addParameter( new JParameter( new JType( locationClass.getName() ), "target" ) );
944         jMethod.addParameter( new JParameter( new JType( locationClass.getName() ), "source" ) );
945         jMethod.addParameter( new JParameter( JType.BOOLEAN, "sourceDominant" ) );
946         sc = jMethod.getSourceCode();
947         sc.add( "if ( source == null )" );
948         sc.add( "{" );
949         sc.addIndented( "return target;" );
950         sc.add( "}" );
951         sc.add( "else if ( target == null )" );
952         sc.add( "{" );
953         sc.addIndented( "return source;" );
954         sc.add( "}" );
955         sc.add( "" );
956         sc.add( locationClass.getName() + " result =" );
957         sc.add( "    new " + locationClass.getName() + "( target.getLineNumber(), target.getColumnNumber()" + (
958             sourceClass != null
959                 ? ", target.get" + capitalise( source.getName() ) + "()"
960                 : "" ) + " );" );
961         sc.add( "" );
962         sc.add( fieldType + " locations;" );
963         sc.add( fieldType + " sourceLocations = source.get" + capitalise( locationsField ) + "();" );
964         sc.add( fieldType + " targetLocations = target.get" + capitalise( locationsField ) + "();" );
965         sc.add( "if ( sourceLocations == null )" );
966         sc.add( "{" );
967         sc.addIndented( "locations = targetLocations;" );
968         sc.add( "}" );
969         sc.add( "else if ( targetLocations == null )" );
970         sc.add( "{" );
971         sc.addIndented( "locations = sourceLocations;" );
972         sc.add( "}" );
973         sc.add( "else" );
974         sc.add( "{" );
975         sc.addIndented( "locations = new " + fieldImpl + "();" );
976         sc.addIndented( "locations.putAll( sourceDominant ? targetLocations : sourceLocations );" );
977         sc.addIndented( "locations.putAll( sourceDominant ? sourceLocations : targetLocations );" );
978         sc.add( "}" );
979         sc.add( "result.set" + capitalise( locationsField ) + "( locations );" );
980         sc.add( "" );
981         sc.add( "return result;" );
982         jClass.addMethod( jMethod );
983 
984         // public static Location merge( Location target, Location source, Collection<Integer> indices )
985         jMethod = new JMethod( "merge", new JType( locationClass.getName() ), null );
986         jMethod.getModifiers().setStatic( true );
987         jMethod.addParameter( new JParameter( new JType( locationClass.getName() ), "target" ) );
988         jMethod.addParameter( new JParameter( new JType( locationClass.getName() ), "source" ) );
989         jMethod.addParameter(
990             new JParameter( new JCollectionType( "java.util.Collection", new JType( "Integer" ), useJava5 ),
991                             "indices" ) );
992         String intWrap = useJava5 ? "Integer.valueOf" : "new Integer";
993         sc = jMethod.getSourceCode();
994         sc.add( "if ( source == null )" );
995         sc.add( "{" );
996         sc.addIndented( "return target;" );
997         sc.add( "}" );
998         sc.add( "else if ( target == null )" );
999         sc.add( "{" );
1000         sc.addIndented( "return source;" );
1001         sc.add( "}" );
1002         sc.add( "" );
1003         sc.add( locationClass.getName() + " result =" );
1004         sc.add( "    new " + locationClass.getName() + "( target.getLineNumber(), target.getColumnNumber()" + (
1005             sourceClass != null
1006                 ? ", target.get" + capitalise( source.getName() ) + "()"
1007                 : "" ) + " );" );
1008         sc.add( "" );
1009         sc.add( fieldType + " locations;" );
1010         sc.add( fieldType + " sourceLocations = source.get" + capitalise( locationsField ) + "();" );
1011         sc.add( fieldType + " targetLocations = target.get" + capitalise( locationsField ) + "();" );
1012         sc.add( "if ( sourceLocations == null )" );
1013         sc.add( "{" );
1014         sc.addIndented( "locations = targetLocations;" );
1015         sc.add( "}" );
1016         sc.add( "else if ( targetLocations == null )" );
1017         sc.add( "{" );
1018         sc.addIndented( "locations = sourceLocations;" );
1019         sc.add( "}" );
1020         sc.add( "else" );
1021         sc.add( "{" );
1022         sc.indent();
1023         sc.add( "locations = new " + fieldImpl + "();" );
1024         sc.add( "for ( java.util.Iterator" + ( useJava5 ? "<Integer>" : "" )
1025                     + " it = indices.iterator(); it.hasNext(); )" );
1026         sc.add( "{" );
1027         sc.indent();
1028         sc.add( locationClass.getName() + " location;" );
1029         sc.add( "Integer index = " + ( useJava5 ? "" : "(Integer) " ) + "it.next();" );
1030         sc.add( "if ( index.intValue() < 0 )" );
1031         sc.add( "{" );
1032         sc.addIndented( "location = sourceLocations.get( " + intWrap + "( ~index.intValue() ) );" );
1033         sc.add( "}" );
1034         sc.add( "else" );
1035         sc.add( "{" );
1036         sc.addIndented( "location = targetLocations.get( index );" );
1037         sc.add( "}" );
1038         sc.add( "locations.put( " + intWrap + "( locations.size() ), location );" );
1039         sc.unindent();
1040         sc.add( "}" );
1041         sc.unindent();
1042         sc.add( "}" );
1043         sc.add( "result.set" + capitalise( locationsField ) + "( locations );" );
1044         sc.add( "" );
1045         sc.add( "return result;" );
1046         jClass.addMethod( jMethod );
1047     }
1048 
1049     /**
1050      * Utility method that adds a period to the end of a string, if the last non-whitespace character of the string is
1051      * not a punctuation mark or an end-tag.
1052      *
1053      * @param string The string to work with
1054      * @return The string that came in but with a period at the end
1055      */
1056     private String appendPeriod( String string )
1057     {
1058         if ( string == null )
1059         {
1060             return string;
1061         }
1062 
1063         String trimmedString = string.trim();
1064         if ( trimmedString.endsWith( "." ) || trimmedString.endsWith( "!" ) || trimmedString.endsWith( "?" )
1065             || trimmedString.endsWith( ">" ) )
1066         {
1067             return string;
1068         }
1069         else
1070         {
1071             return string + ".";
1072         }
1073     }
1074 
1075     private String createHashCodeForField( ModelField identifier )
1076     {
1077         String name = identifier.getName();
1078         String type = identifier.getType();
1079 
1080         if ( "boolean".equals( type ) )
1081         {
1082             return "( " + name + " ? 0 : 1 )";
1083         }
1084         else if ( "byte".equals( type ) || "char".equals( type ) || "short".equals( type ) || "int".equals( type ) )
1085         {
1086             return "(int) " + name;
1087         }
1088         else if ( "long".equals( type ) )
1089         {
1090             return "(int) ( " + name + " ^ ( " + name + " >>> 32 ) )";
1091         }
1092         else if ( "float".equals( type ) )
1093         {
1094             return "Float.floatToIntBits( " + name + " )";
1095         }
1096         else if ( "double".equals( type ) )
1097         {
1098             return "(int) ( Double.doubleToLongBits( " + identifier.getName() + " ) ^ ( Double.doubleToLongBits( "
1099                 + identifier.getName() + " ) >>> 32 ) )";
1100         }
1101         else
1102         {
1103             return "( " + name + " != null ? " + name + ".hashCode() : 0 )";
1104         }
1105     }
1106 
1107     private JField createField( ModelField modelField )
1108         throws ModelloException
1109     {
1110         JType type;
1111 
1112         String baseType = modelField.getType();
1113         if ( modelField.isArray() )
1114         {
1115             // remove [] at the end of the type
1116             baseType = baseType.substring( 0, baseType.length() - 2 );
1117         }
1118 
1119         if ( "boolean".equals( baseType ) )
1120         {
1121             type = JType.BOOLEAN;
1122         }
1123         else if ( "byte".equals( baseType ) )
1124         {
1125             type = JType.BYTE;
1126         }
1127         else if ( "char".equals( baseType ) )
1128         {
1129             type = JType.CHAR;
1130         }
1131         else if ( "double".equals( baseType ) )
1132         {
1133             type = JType.DOUBLE;
1134         }
1135         else if ( "float".equals( baseType ) )
1136         {
1137             type = JType.FLOAT;
1138         }
1139         else if ( "int".equals( baseType ) )
1140         {
1141             type = JType.INT;
1142         }
1143         else if ( "short".equals( baseType ) )
1144         {
1145             type = JType.SHORT;
1146         }
1147         else if ( "long".equals( baseType ) )
1148         {
1149             type = JType.LONG;
1150         }
1151         else if ( "Date".equals( baseType ) )
1152         {
1153             type = new JClass( "java.util.Date" );
1154         }
1155         else if ( "DOM".equals( baseType ) )
1156         {
1157             // TODO: maybe DOM is not how to specify it in the model, but just Object and markup Xpp3Dom for the
1158             // Xpp3Reader?
1159             // not sure how we'll treat it for the other sources, eg sql.
1160             type = new JClass( "Object" );
1161         }
1162         else
1163         {
1164             type = new JClass( baseType );
1165         }
1166 
1167         if ( modelField.isArray() )
1168         {
1169             type = new JArrayType( type, useJava5 );
1170         }
1171 
1172         JField field = new JField( type, modelField.getName() );
1173 
1174         if ( modelField.isModelVersionField() )
1175         {
1176             field.setInitString( "\"" + getGeneratedVersion() + "\"" );
1177         }
1178 
1179         if ( modelField.getDefaultValue() != null )
1180         {
1181             field.setInitString( getJavaDefaultValue( modelField ) );
1182         }
1183 
1184         if ( StringUtils.isNotEmpty( modelField.getDescription() ) )
1185         {
1186             field.setComment( appendPeriod( modelField.getDescription() ) );
1187         }
1188 
1189         if ( useJava5 && !modelField.getAnnotations().isEmpty() )
1190         {
1191             for ( String annotation : modelField.getAnnotations() )
1192             {
1193                 field.appendAnnotation( annotation );
1194             }
1195         }
1196 
1197         return field;
1198     }
1199 
1200     private void createField( JClass jClass, ModelField modelField )
1201         throws ModelloException
1202     {
1203         JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) modelField.getMetadata( JavaFieldMetadata.ID );
1204 
1205         JField field = createField( modelField );
1206 
1207         jClass.addField( field );
1208 
1209         if ( javaFieldMetadata.isGetter() )
1210         {
1211             jClass.addMethod( createGetter( field, modelField ) );
1212         }
1213 
1214         if ( javaFieldMetadata.isSetter() )
1215         {
1216             jClass.addMethod( createSetter( field, modelField ) );
1217         }
1218     }
1219 
1220     private JMethod createGetter( JField field, ModelField modelField )
1221     {
1222         String propertyName = capitalise( field.getName() );
1223 
1224         JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) modelField.getMetadata( JavaFieldMetadata.ID );
1225 
1226         String prefix = javaFieldMetadata.isBooleanGetter() ? "is" : "get";
1227 
1228         JType returnType = field.getType();
1229         String interfaceCast = "";
1230 
1231         if ( modelField instanceof ModelAssociation )
1232         {
1233             ModelAssociation modelAssociation = (ModelAssociation) modelField;
1234 
1235             JavaAssociationMetadata javaAssociationMetadata =
1236                 (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1237 
1238             if ( StringUtils.isNotEmpty( javaAssociationMetadata.getInterfaceName() )
1239                 && !javaFieldMetadata.isBooleanGetter() )
1240             {
1241                 returnType = new JClass( javaAssociationMetadata.getInterfaceName() );
1242 
1243                 interfaceCast = "(" + javaAssociationMetadata.getInterfaceName() + ") ";
1244             }
1245         }
1246 
1247         JMethod getter = new JMethod( prefix + propertyName, returnType, null );
1248 
1249         StringBuffer comment = new StringBuffer( "Get " );
1250         if ( StringUtils.isEmpty( modelField.getDescription() ) )
1251         {
1252             comment.append( "the " );
1253             comment.append( field.getName() );
1254             comment.append( " field" );
1255         }
1256         else
1257         {
1258             comment.append( StringUtils.lowercaseFirstLetter( modelField.getDescription().trim() ) );
1259         }
1260         getter.getJDocComment().setComment( appendPeriod( comment.toString() ) );
1261 
1262         getter.getSourceCode().add( "return " + interfaceCast + "this." + field.getName() + ";" );
1263 
1264         return getter;
1265     }
1266 
1267     private JMethod createSetter( JField field, ModelField modelField )
1268         throws ModelloException
1269     {
1270         return createSetter( field, modelField, false );
1271     }
1272 
1273     // since 1.8
1274     private JMethod createSetter( JField field, ModelField modelField, boolean isBuilderMethod )
1275         throws ModelloException
1276     {
1277         String propertyName = capitalise( field.getName() );
1278 
1279         JMethod setter;
1280         if ( isBuilderMethod )
1281         {
1282             setter = new JMethod( "set" + propertyName, new JClass( "Builder" ), "this builder instance" );
1283         }
1284         else
1285         {
1286             setter = new JMethod( "set" + propertyName );
1287         }
1288 
1289         StringBuffer comment = new StringBuffer( "Set " );
1290         if ( StringUtils.isEmpty( modelField.getDescription() ) )
1291         {
1292             comment.append( "the " );
1293             comment.append( field.getName() );
1294             comment.append( " field" );
1295         }
1296         else
1297         {
1298             comment.append( StringUtils.lowercaseFirstLetter( modelField.getDescription().trim() ) );
1299         }
1300         setter.getJDocComment().setComment( appendPeriod( comment.toString() ) );
1301 
1302         JType parameterType = getDesiredType( modelField, false );
1303 
1304         setter.addParameter( new JParameter( parameterType, field.getName() ) );
1305 
1306         JSourceCode sc = setter.getSourceCode();
1307 
1308         if ( modelField instanceof ModelAssociation )
1309         {
1310             ModelAssociation modelAssociation = (ModelAssociation) modelField;
1311 
1312             JavaAssociationMetadata javaAssociationMetadata =
1313                 (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1314 
1315             boolean isOneMultiplicity =
1316                 isBidirectionalAssociation( modelAssociation ) && modelAssociation.isOneMultiplicity();
1317 
1318             if ( isOneMultiplicity && javaAssociationMetadata.isBidi() )
1319             {
1320                 sc.add( "if ( this." + field.getName() + " != null )" );
1321 
1322                 sc.add( "{" );
1323 
1324                 sc.indent();
1325 
1326                 sc.add( "this." + field.getName() + ".break" + modelAssociation.getModelClass().getName()
1327                             + "Association( this );" );
1328 
1329                 sc.unindent();
1330 
1331                 sc.add( "}" );
1332 
1333                 sc.add( "" );
1334             }
1335 
1336             String interfaceCast = "";
1337 
1338             if ( StringUtils.isNotEmpty( javaAssociationMetadata.getInterfaceName() )
1339                 && modelAssociation.isOneMultiplicity() )
1340             {
1341                 interfaceCast = "(" + field.getType().getName() + ") ";
1342 
1343                 createClassCastAssertion( sc, modelAssociation, "set" );
1344             }
1345 
1346             sc.add( "this." + field.getName() + " = " + interfaceCast + field.getName() + ";" );
1347 
1348             if ( isOneMultiplicity && javaAssociationMetadata.isBidi() )
1349             {
1350                 sc.add( "" );
1351 
1352                 sc.add( "if ( " + field.getName() + " != null )" );
1353 
1354                 sc.add( "{" );
1355 
1356                 sc.indent();
1357 
1358                 sc.add( "this." + field.getName() + ".create" + modelAssociation.getModelClass().getName()
1359                             + "Association( this );" );
1360 
1361                 sc.unindent();
1362 
1363                 sc.add( "}" );
1364             }
1365         }
1366         else
1367         {
1368             sc.add( "this." + field.getName() + " = " + field.getName() + ";" );
1369         }
1370 
1371         if ( isBuilderMethod )
1372         {
1373             sc.add( "return this;" );
1374         }
1375 
1376         return setter;
1377     }
1378 
1379     private void createClassCastAssertion( JSourceCode sc, ModelAssociation modelAssociation, String crudModifier )
1380         throws ModelloException
1381     {
1382         JavaAssociationMetadata javaAssociationMetadata =
1383             (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1384 
1385         if ( StringUtils.isEmpty( javaAssociationMetadata.getInterfaceName() ) )
1386         {
1387             return; // java.useInterface feature not used, no class cast assertion needed
1388         }
1389 
1390         String propertyName = capitalise( modelAssociation.getName() );
1391 
1392         JField field = createField( modelAssociation );
1393         String fieldName = field.getName();
1394         JType type = new JClass( modelAssociation.getTo() );
1395 
1396         if ( modelAssociation.isManyMultiplicity() )
1397         {
1398             fieldName = uncapitalise( modelAssociation.getTo() );
1399         }
1400 
1401         String instanceName = type.getName();
1402 
1403         // Add sane class cast exception message
1404         // When will sun ever fix this?
1405 
1406         sc.add( "if ( " + fieldName + " != null && !( " + fieldName + " instanceof " + instanceName + " ) )" );
1407 
1408         sc.add( "{" );
1409 
1410         sc.indent();
1411 
1412         sc.add( "throw new ClassCastException( \"" + modelAssociation.getModelClass().getName() + "." + crudModifier
1413                     + propertyName + "( " + fieldName + " ) parameter must be instanceof \" + " + instanceName
1414                     + ".class.getName() );" );
1415 
1416         sc.unindent();
1417 
1418         sc.add( "}" );
1419     }
1420 
1421     private void createAssociation( JClass jClass, ModelAssociation modelAssociation, JSourceCode jConstructorSource )
1422         throws ModelloException
1423     {
1424         JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) modelAssociation.getMetadata( JavaFieldMetadata.ID );
1425 
1426         JavaAssociationMetadata javaAssociationMetadata = getJavaAssociationMetadata( modelAssociation );
1427 
1428         if ( modelAssociation.isManyMultiplicity() )
1429         {
1430             JType componentType = getComponentType( modelAssociation, javaAssociationMetadata );
1431 
1432             String defaultValue = getDefaultValue( modelAssociation, componentType );
1433 
1434             JType type;
1435             if ( modelAssociation.isGenericType() )
1436             {
1437                 type = new JCollectionType( modelAssociation.getType(), componentType, useJava5 );
1438             }
1439             else if ( ModelDefault.MAP.equals( modelAssociation.getType() ) )
1440             {
1441                 JMapType mapType = new JMapType( modelAssociation.getType(), defaultValue, componentType, useJava5 );
1442                 defaultValue = mapType.getInstanceName();
1443                 type = mapType;
1444             }
1445             else
1446             {
1447                 type = new JClass( modelAssociation.getType() );
1448             }
1449 
1450             JField jField = new JField( type, modelAssociation.getName() );
1451 
1452             if ( !isEmpty( modelAssociation.getComment() ) )
1453             {
1454                 jField.setComment( modelAssociation.getComment() );
1455             }
1456 
1457             if ( useJava5 && !modelAssociation.getAnnotations().isEmpty() )
1458             {
1459                 for ( String annotation : modelAssociation.getAnnotations() )
1460                 {
1461                     jField.appendAnnotation( annotation );
1462                 }
1463             }
1464 
1465             if ( StringUtils.equals( javaAssociationMetadata.getInitializationMode(),
1466                                      JavaAssociationMetadata.FIELD_INIT ) )
1467             {
1468                 jField.setInitString( defaultValue );
1469             }
1470 
1471             if ( StringUtils.equals( javaAssociationMetadata.getInitializationMode(),
1472                                      JavaAssociationMetadata.CONSTRUCTOR_INIT ) )
1473             {
1474                 jConstructorSource.add( "this." + jField.getName() + " = " + defaultValue + ";" );
1475             }
1476 
1477             jClass.addField( jField );
1478 
1479             if ( javaFieldMetadata.isGetter() )
1480             {
1481                 String propertyName = capitalise( jField.getName() );
1482 
1483                 JMethod getter = new JMethod( "get" + propertyName, jField.getType(), null );
1484 
1485                 JSourceCode sc = getter.getSourceCode();
1486 
1487                 if ( StringUtils.equals( javaAssociationMetadata.getInitializationMode(),
1488                                          JavaAssociationMetadata.LAZY_INIT ) )
1489                 {
1490                     sc.add( "if ( this." + jField.getName() + " == null )" );
1491 
1492                     sc.add( "{" );
1493 
1494                     sc.indent();
1495 
1496                     sc.add( "this." + jField.getName() + " = " + defaultValue + ";" );
1497 
1498                     sc.unindent();
1499 
1500                     sc.add( "}" );
1501 
1502                     sc.add( "" );
1503                 }
1504 
1505                 sc.add( "return this." + jField.getName() + ";" );
1506 
1507                 jClass.addMethod( getter );
1508             }
1509 
1510             if ( javaFieldMetadata.isSetter() )
1511             {
1512                 jClass.addMethod( createSetter( jField, modelAssociation ) );
1513             }
1514 
1515             if ( javaAssociationMetadata.isAdder() )
1516             {
1517                 createAdder( modelAssociation, jClass );
1518             }
1519         }
1520         else
1521         {
1522             createField( jClass, modelAssociation );
1523         }
1524 
1525         if ( isBidirectionalAssociation( modelAssociation ) )
1526         {
1527             if ( javaAssociationMetadata.isBidi() )
1528             {
1529                 createCreateAssociation( jClass, modelAssociation );
1530                 createBreakAssociation( jClass, modelAssociation );
1531             }
1532         }
1533     }
1534 
1535     private String getDefaultValue( ModelAssociation modelAssociation, JType componentType )
1536     {
1537         String defaultValue = getDefaultValue( modelAssociation );
1538 
1539         if ( modelAssociation.isGenericType() )
1540         {
1541             ModelDefault modelDefault = getModel().getDefault( modelAssociation.getType() );
1542 
1543             if ( useJava5 )
1544             {
1545                 defaultValue =
1546                     StringUtils.replace( modelDefault.getValue(), "<?>", "<" + componentType.getName() + ">" );
1547             }
1548             else
1549             {
1550                 defaultValue =
1551                     StringUtils.replace( modelDefault.getValue(), "<?>", "/*<" + componentType.getName() + ">*/" );
1552             }
1553         }
1554         return defaultValue;
1555     }
1556 
1557     private JavaAssociationMetadata getJavaAssociationMetadata( ModelAssociation modelAssociation )
1558         throws ModelloException
1559     {
1560         JavaAssociationMetadata javaAssociationMetadata =
1561             (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1562 
1563         if ( !JavaAssociationMetadata.INIT_TYPES.contains( javaAssociationMetadata.getInitializationMode() ) )
1564         {
1565             throw new ModelloException(
1566                 "The Java Modello Generator cannot use '" + javaAssociationMetadata.getInitializationMode()
1567                     + "' as a <association java.init=\"" + javaAssociationMetadata.getInitializationMode() + "\"> "
1568                     + "value, the only the following are acceptable " + JavaAssociationMetadata.INIT_TYPES );
1569         }
1570         return javaAssociationMetadata;
1571     }
1572 
1573     private JType getComponentType( ModelAssociation modelAssociation, JavaAssociationMetadata javaAssociationMetadata )
1574     {
1575         JType componentType;
1576         if ( javaAssociationMetadata.getInterfaceName() != null )
1577         {
1578             componentType = new JClass( javaAssociationMetadata.getInterfaceName() );
1579         }
1580         else
1581         {
1582             componentType = new JClass( modelAssociation.getTo() );
1583         }
1584         return componentType;
1585     }
1586 
1587     private void createCreateAssociation( JClass jClass, ModelAssociation modelAssociation )
1588     {
1589         JMethod createMethod = new JMethod( "create" + modelAssociation.getTo() + "Association" );
1590 
1591         JavaAssociationMetadata javaAssociationMetadata =
1592             (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1593 
1594         createMethod.addParameter(
1595             new JParameter( new JClass( modelAssociation.getTo() ), uncapitalise( modelAssociation.getTo() ) ) );
1596 
1597         // TODO: remove after tested
1598         // createMethod.addException( new JClass( "Exception" ) );
1599 
1600         JSourceCode sc = createMethod.getSourceCode();
1601 
1602         if ( modelAssociation.isOneMultiplicity() )
1603         {
1604             if ( javaAssociationMetadata.isBidi() )
1605             {
1606                 sc.add( "if ( this." + modelAssociation.getName() + " != null )" );
1607 
1608                 sc.add( "{" );
1609 
1610                 sc.indent();
1611 
1612                 sc.add(
1613                     "break" + modelAssociation.getTo() + "Association( this." + modelAssociation.getName() + " );" );
1614 
1615                 sc.unindent();
1616 
1617                 sc.add( "}" );
1618 
1619                 sc.add( "" );
1620             }
1621 
1622             sc.add( "this." + modelAssociation.getName() + " = " + uncapitalise( modelAssociation.getTo() ) + ";" );
1623         }
1624         else
1625         {
1626             jClass.addImport( "java.util.Collection" );
1627 
1628             sc.add( "Collection " + modelAssociation.getName() + " = get" + capitalise( modelAssociation.getName() )
1629                         + "();" );
1630 
1631             sc.add( "" );
1632 
1633             sc.add( "if ( " + modelAssociation.getName() + ".contains( " + uncapitalise( modelAssociation.getTo() )
1634                         + " ) )" );
1635 
1636             sc.add( "{" );
1637 
1638             sc.indent();
1639 
1640             sc.add( "throw new IllegalStateException( \"" + uncapitalise( modelAssociation.getTo() )
1641                         + " is already assigned.\" );" );
1642 
1643             sc.unindent();
1644 
1645             sc.add( "}" );
1646 
1647             sc.add( "" );
1648 
1649             sc.add( modelAssociation.getName() + ".add( " + uncapitalise( modelAssociation.getTo() ) + " );" );
1650         }
1651 
1652         jClass.addMethod( createMethod );
1653     }
1654 
1655     private void createBreakAssociation( JClass jClass, ModelAssociation modelAssociation )
1656     {
1657         JSourceCode sc;
1658         JMethod breakMethod = new JMethod( "break" + modelAssociation.getTo() + "Association" );
1659 
1660         breakMethod.addParameter(
1661             new JParameter( new JClass( modelAssociation.getTo() ), uncapitalise( modelAssociation.getTo() ) ) );
1662 
1663         // TODO: remove after tested
1664         // breakMethod.addException( new JClass( "Exception" ) );
1665 
1666         sc = breakMethod.getSourceCode();
1667 
1668         if ( modelAssociation.isOneMultiplicity() )
1669         {
1670             sc.add(
1671                 "if ( this." + modelAssociation.getName() + " != " + uncapitalise( modelAssociation.getTo() ) + " )" );
1672 
1673             sc.add( "{" );
1674 
1675             sc.indent();
1676 
1677             sc.add( "throw new IllegalStateException( \"" + uncapitalise( modelAssociation.getTo() )
1678                         + " isn't associated.\" );" );
1679 
1680             sc.unindent();
1681 
1682             sc.add( "}" );
1683 
1684             sc.add( "" );
1685 
1686             sc.add( "this." + modelAssociation.getName() + " = null;" );
1687         }
1688         else
1689         {
1690             JavaAssociationMetadata javaAssociationMetadata =
1691                             (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1692 
1693             String reference;
1694 
1695             if ( JavaAssociationMetadata.LAZY_INIT.equals( javaAssociationMetadata.getInitializationMode() ) )
1696             {
1697                 reference = "get" + capitalise( modelAssociation.getName() ) + "()";
1698             }
1699             else
1700             {
1701                 reference = modelAssociation.getName();
1702             }
1703 
1704             sc.add( "if ( !" + reference + ".contains( " + uncapitalise(
1705                 modelAssociation.getTo() ) + " ) )" );
1706 
1707             sc.add( "{" );
1708 
1709             sc.indent();
1710 
1711             sc.add( "throw new IllegalStateException( \"" + uncapitalise( modelAssociation.getTo() )
1712                         + " isn't associated.\" );" );
1713 
1714             sc.unindent();
1715 
1716             sc.add( "}" );
1717 
1718             sc.add( "" );
1719 
1720             sc.add( reference + ".remove( " + uncapitalise( modelAssociation.getTo() ) + " );" );
1721         }
1722 
1723         jClass.addMethod( breakMethod );
1724     }
1725 
1726     private void createAdder( ModelAssociation modelAssociation, JClass jClass )
1727         throws ModelloException
1728     {
1729         createAdder( modelAssociation, jClass, false );
1730     }
1731 
1732     /*
1733      * since 1.8
1734      */
1735     private void createAdder( ModelAssociation modelAssociation, JClass jClass, boolean isBuilderMethod )
1736         throws ModelloException
1737     {
1738         String fieldName = modelAssociation.getName();
1739 
1740         JavaAssociationMetadata javaAssociationMetadata =
1741             (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1742 
1743         String parameterName = uncapitalise( modelAssociation.getTo() );
1744         String implementationParameterName = parameterName;
1745 
1746         boolean bidirectionalAssociation = isBidirectionalAssociation( modelAssociation );
1747 
1748         JType addType;
1749 
1750         if ( StringUtils.isNotEmpty( javaAssociationMetadata.getInterfaceName() ) )
1751         {
1752             addType = new JClass( javaAssociationMetadata.getInterfaceName() );
1753             implementationParameterName = "( (" + modelAssociation.getTo() + ") " + parameterName + " )";
1754         }
1755         else if ( modelAssociation.getToClass() != null )
1756         {
1757             addType = new JClass( modelAssociation.getToClass().getName() );
1758         }
1759         else
1760         {
1761             addType = new JClass( "String" );
1762         }
1763 
1764         if ( modelAssociation.getType().equals( ModelDefault.PROPERTIES ) || modelAssociation.getType().equals(
1765             ModelDefault.MAP ) )
1766         {
1767             String adderName = "add" + capitalise( singular( fieldName ) );
1768 
1769             JMethod adder;
1770             if ( isBuilderMethod )
1771             {
1772                 adder = new JMethod( adderName, new JClass( "Builder" ), "this builder instance" );
1773             }
1774             else
1775             {
1776                 adder = new JMethod( adderName );
1777             }
1778 
1779             if ( modelAssociation.getType().equals( ModelDefault.MAP ) )
1780             {
1781                 adder.addParameter( new JParameter( new JClass( "Object" ), "key" ) );
1782             }
1783             else
1784             {
1785                 adder.addParameter( new JParameter( new JClass( "String" ), "key" ) );
1786             }
1787 
1788             adder.addParameter( new JParameter( new JClass( modelAssociation.getTo() ), "value" ) );
1789 
1790             StringBuilder adderCode = new StringBuilder();
1791 
1792             if ( JavaAssociationMetadata.LAZY_INIT.equals( javaAssociationMetadata.getInitializationMode() )
1793                     && !isBuilderMethod )
1794             {
1795                 adderCode.append( "get" ).append( capitalise( fieldName ) ).append( "()" );
1796             }
1797             else
1798             {
1799                 adderCode.append( fieldName );
1800             }
1801 
1802             adderCode.append( ".put( key, value );" );
1803 
1804             adder.getSourceCode().add( adderCode.toString() );
1805 
1806             if ( isBuilderMethod )
1807             {
1808                 adder.getSourceCode().add( "return this;" );
1809             }
1810 
1811             jClass.addMethod( adder );
1812         }
1813         else
1814         {
1815             String adderName = "add" + singular( capitalise( singular( fieldName ) ) );
1816 
1817             JMethod adder;
1818             if ( isBuilderMethod )
1819             {
1820                 adder = new JMethod( adderName, new JClass( "Builder" ), "this builder instance" );
1821             }
1822             else
1823             {
1824                 adder = new JMethod( adderName );
1825             }
1826 
1827             adder.addParameter( new JParameter( addType, parameterName ) );
1828 
1829             createClassCastAssertion( adder.getSourceCode(), modelAssociation, "add" );
1830 
1831             StringBuilder adderCode = new StringBuilder();
1832 
1833             if ( JavaAssociationMetadata.LAZY_INIT.equals( javaAssociationMetadata.getInitializationMode() )
1834                     && !isBuilderMethod )
1835             {
1836                 adderCode.append( "get" ).append( capitalise( fieldName ) ).append( "()" );
1837             }
1838             else
1839             {
1840                 adderCode.append( fieldName );
1841             }
1842 
1843             adderCode.append( ".add( " )
1844                      .append( implementationParameterName )
1845                      .append( " );" );
1846 
1847             adder.getSourceCode().add( adderCode.toString() );
1848 
1849             if ( bidirectionalAssociation && javaAssociationMetadata.isBidi() && !isBuilderMethod )
1850             {
1851                 // TODO: remove after tested
1852                 // adder.addException( new JClass( "Exception" ) );
1853 
1854                 adder.getSourceCode().add(
1855                     implementationParameterName + ".create" + modelAssociation.getModelClass().getName()
1856                         + "Association( this );" );
1857             }
1858 
1859             if ( isBuilderMethod )
1860             {
1861                 adder.getSourceCode().add( "return this;" );
1862             }
1863 
1864             jClass.addMethod( adder );
1865 
1866             // don't add the remover in the inner Builder class
1867             if ( isBuilderMethod )
1868             {
1869                 return;
1870             }
1871 
1872             JMethod remover = new JMethod( "remove" + singular( capitalise( fieldName ) ) );
1873 
1874             remover.addParameter( new JParameter( addType, parameterName ) );
1875 
1876             createClassCastAssertion( remover.getSourceCode(), modelAssociation, "remove" );
1877 
1878             if ( bidirectionalAssociation && javaAssociationMetadata.isBidi() )
1879             {
1880                 // TODO: remove after tested
1881                 // remover.addException( new JClass( "Exception" ) );
1882 
1883                 remover.getSourceCode().add(
1884                     parameterName + ".break" + modelAssociation.getModelClass().getName() + "Association( this );" );
1885             }
1886 
1887             String reference;
1888 
1889             if ( JavaAssociationMetadata.LAZY_INIT.equals( javaAssociationMetadata.getInitializationMode() ) )
1890             {
1891                 reference = "get" + capitalise( fieldName ) + "()";
1892             }
1893             else
1894             {
1895                 reference = fieldName;
1896             }
1897 
1898             remover.getSourceCode().add( reference + ".remove( " + implementationParameterName + " );" );
1899 
1900             jClass.addMethod( remover );
1901         }
1902     }
1903 
1904     private boolean isBidirectionalAssociation( ModelAssociation association )
1905     {
1906         Model model = association.getModelClass().getModel();
1907 
1908         if ( !isClassInModel( association.getTo(), model ) )
1909         {
1910             return false;
1911         }
1912 
1913         ModelClass toClass = association.getToClass();
1914 
1915         for ( ModelField modelField : toClass.getFields( getGeneratedVersion() ) )
1916         {
1917             if ( !( modelField instanceof ModelAssociation ) )
1918             {
1919                 continue;
1920             }
1921 
1922             ModelAssociation modelAssociation = (ModelAssociation) modelField;
1923 
1924             if ( association == modelAssociation )
1925             {
1926                 continue;
1927             }
1928 
1929             if ( !isClassInModel( modelAssociation.getTo(), model ) )
1930             {
1931                 continue;
1932             }
1933 
1934             ModelClass totoClass = modelAssociation.getToClass();
1935 
1936             if ( association.getModelClass().equals( totoClass ) )
1937             {
1938                 return true;
1939             }
1940         }
1941 
1942         return false;
1943     }
1944 
1945     private JType getDesiredType( ModelField modelField, boolean useTo )
1946         throws ModelloException
1947     {
1948         JField field = createField( modelField );
1949         JType type = field.getType();
1950 
1951         if ( modelField instanceof ModelAssociation )
1952         {
1953             ModelAssociation modelAssociation = (ModelAssociation) modelField;
1954             JavaAssociationMetadata javaAssociationMetadata =
1955                 (JavaAssociationMetadata) modelAssociation.getAssociationMetadata( JavaAssociationMetadata.ID );
1956 
1957             if ( StringUtils.isNotEmpty( javaAssociationMetadata.getInterfaceName() )
1958                 && !modelAssociation.isManyMultiplicity() )
1959             {
1960                 type = new JClass( javaAssociationMetadata.getInterfaceName() );
1961             }
1962             else if ( modelAssociation.isManyMultiplicity() && modelAssociation.isGenericType() )
1963             {
1964                 JType componentType = getComponentType( modelAssociation, javaAssociationMetadata );
1965                 type = new JCollectionType( modelAssociation.getType(), componentType, useJava5 );
1966             }
1967             else if ( useTo )
1968             {
1969                 type = new JClass( modelAssociation.getTo() );
1970             }
1971         }
1972 
1973         return type;
1974     }
1975 
1976     private void addParameter( JMethodSignature jMethod, String type, String name, String comment )
1977     {
1978         jMethod.addParameter( new JParameter( new JType( type ), name ) );
1979         jMethod.getJDocComment().getParamDescriptor( name ).setDescription( comment );
1980     }
1981 
1982     // ----------------------------------------------------------------------
1983     // Model.Builder
1984     // since 1.8
1985     // ----------------------------------------------------------------------
1986 
1987     private void generateBuilder( ModelClass modelClass, JClass builderClass, JConstructor outherClassConstructor )
1988         throws ModelloException
1989     {
1990         builderClass.getModifiers().setStatic( true );
1991         builderClass.getModifiers().setFinal( true );
1992 
1993         ModelClass reference = modelClass;
1994 
1995         // traverse the whole modelClass hierarchy to create the nested Builder
1996         while ( reference != null )
1997         {
1998             // create builder setters methods
1999             for ( ModelField modelField : reference.getFields( getGeneratedVersion() ) )
2000             {
2001                 if ( modelField instanceof ModelAssociation )
2002                 {
2003                     createBuilderAssociation( builderClass, (ModelAssociation) modelField );
2004                 }
2005                 else
2006                 {
2007                     createBuilderField( builderClass, modelField );
2008                 }
2009             }
2010 
2011             if ( reference.hasSuperClass() )
2012             {
2013                 reference = reference.getModel().getClass( reference.getSuperClass(), getGeneratedVersion() );
2014             }
2015             else
2016             {
2017                 reference = null;
2018             }
2019         }
2020 
2021         // create and add the Model#build() method
2022         JMethod build = new JMethod( "build", new JClass( modelClass.getName() ), "A new <code>"
2023                                                                                   + modelClass.getName()
2024                                                                                   + "</code> instance" );
2025         build.getJDocComment().setComment( "Creates a new <code>"
2026                                            + modelClass.getName()
2027                                            + "</code> instance." );
2028 
2029         JSourceCode sc = build.getSourceCode();
2030 
2031         createInstanceAndSetProperties( modelClass, outherClassConstructor, sc );
2032 
2033         builderClass.addMethod( build );
2034     }
2035 
2036     private void createInstanceAndSetProperties( ModelClass modelClass, JConstructor constructor, JSourceCode sc )
2037         throws ModelloException
2038     {
2039         final Set<String> ctorArgs = new HashSet<String>();
2040 
2041         StringBuilder ctor = new StringBuilder( modelClass.getName() )
2042                                  .append( " instance = new " )
2043                                  .append( modelClass.getName() )
2044                                  .append( '(' );
2045 
2046         // understand if default empty ctor can be used or if it requires parameters
2047         if ( constructor != null )
2048         {
2049             JParameter[] parameters = constructor.getParameters();
2050             for ( int i = 0; i < parameters.length; i++ )
2051             {
2052                 if ( i > 0 )
2053                 {
2054                     ctor.append( ',' );
2055                 }
2056 
2057                 JParameter parameter = parameters[i];
2058 
2059                 ctor.append( ' ' )
2060                     .append( parameter.getName() )
2061                     .append( ' ' );
2062 
2063                 ctorArgs.add( parameter.getName() );
2064             }
2065         }
2066 
2067         ctor.append( ");" );
2068 
2069         sc.add( ctor.toString() );
2070 
2071         ModelClass reference = modelClass;
2072 
2073         // traverse the whole modelClass hierarchy to create the nested Builder instance
2074         while ( reference != null )
2075         {
2076             // collect parameters and set them in the instance object
2077             for ( ModelField modelField : reference.getFields( getGeneratedVersion() ) )
2078             {
2079                 if ( modelField instanceof ModelAssociation )
2080                 {
2081                     ModelAssociation modelAssociation = (ModelAssociation) modelField;
2082                     JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) modelField.getMetadata( JavaFieldMetadata.ID );
2083                     JavaAssociationMetadata javaAssociationMetadata = getJavaAssociationMetadata( modelAssociation );
2084 
2085                     if ( modelAssociation.isManyMultiplicity()
2086                          && !javaFieldMetadata.isGetter()
2087                          && !javaFieldMetadata.isSetter()
2088                          && !javaAssociationMetadata.isAdder() )
2089                     {
2090                         throw new ModelloException( "Exception while generating Java, Model inconsistency found: impossible to generate '"
2091                                                     + modelClass.getName()
2092                                                     + ".Builder#build()' method, '"
2093                                                     + modelClass.getName()
2094                                                     + "."
2095                                                     + modelAssociation.getName()
2096                                                     + "' field ("
2097                                                     + modelAssociation.getType()
2098                                                     + ") cannot be set, no getter/setter/adder method available." );
2099                     }
2100 
2101                     createSetBuilderAssociationToInstance( ctorArgs, modelAssociation, sc );
2102                 }
2103                 else
2104                 {
2105                     createSetBuilderFieldToInstance( ctorArgs, modelField, sc );
2106                 }
2107             }
2108 
2109             if ( reference.hasSuperClass() )
2110             {
2111                 reference = reference.getModel().getClass( reference.getSuperClass(), getGeneratedVersion() );
2112             }
2113             else
2114             {
2115                 reference = null;
2116             }
2117         }
2118 
2119         sc.add( "return instance;" );
2120     }
2121 
2122     private void createBuilderField( JClass jClass, ModelField modelField )
2123         throws ModelloException
2124     {
2125         JField field = createField( modelField );
2126 
2127         jClass.addField( field );
2128 
2129         jClass.addMethod( createSetter( field, modelField, true ) );
2130     }
2131 
2132     private boolean createSetBuilderFieldToInstance( Set<String> ctorArgs, ModelField modelField, JSourceCode sc )
2133         throws ModelloException
2134     {
2135         JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) modelField.getMetadata( JavaFieldMetadata.ID );
2136 
2137         // if it is not already set by the ctor and if the setter method is available
2138         if ( !ctorArgs.contains( modelField.getName() ) && javaFieldMetadata.isSetter() )
2139         {
2140             sc.add( "instance.set"
2141                     + capitalise( modelField.getName() )
2142                     + "( "
2143                     + modelField.getName()
2144                     + " );" );
2145             return true;
2146         }
2147 
2148         return false;
2149     }
2150 
2151     private void createBuilderAssociation( JClass jClass, ModelAssociation modelAssociation )
2152         throws ModelloException
2153     {
2154         JavaAssociationMetadata javaAssociationMetadata = getJavaAssociationMetadata( modelAssociation );
2155 
2156         if ( modelAssociation.isManyMultiplicity() )
2157         {
2158             JType componentType = getComponentType( modelAssociation, javaAssociationMetadata );
2159 
2160             String defaultValue = getDefaultValue( modelAssociation, componentType );
2161 
2162             JType type;
2163             if ( modelAssociation.isGenericType() )
2164             {
2165                 type = new JCollectionType( modelAssociation.getType(), componentType, useJava5 );
2166             }
2167             else if ( ModelDefault.MAP.equals( modelAssociation.getType() ) )
2168             {
2169                 JMapType mapType = new JMapType( modelAssociation.getType(), defaultValue, componentType, useJava5 );
2170                 defaultValue = mapType.getInstanceName();
2171                 type = mapType;
2172             }
2173             else
2174             {
2175                 type = new JClass( modelAssociation.getType() );
2176             }
2177 
2178             JField jField = new JField( type, modelAssociation.getName() );
2179             jField.getModifiers().setFinal( true );
2180 
2181             if ( !isEmpty( modelAssociation.getComment() ) )
2182             {
2183                 jField.setComment( modelAssociation.getComment() );
2184             }
2185 
2186             if ( useJava5 && !modelAssociation.getAnnotations().isEmpty() )
2187             {
2188                 for ( String annotation : modelAssociation.getAnnotations() )
2189                 {
2190                     jField.appendAnnotation( annotation );
2191                 }
2192             }
2193 
2194             jField.setInitString( defaultValue );
2195 
2196             jClass.addField( jField );
2197 
2198             createAdder( modelAssociation, jClass, true );
2199         }
2200         else
2201         {
2202             createBuilderField( jClass, modelAssociation );
2203         }
2204     }
2205 
2206     private void createSetBuilderAssociationToInstance( Set<String> ctorArgs, ModelAssociation modelAssociation, JSourceCode sc )
2207         throws ModelloException
2208     {
2209         if ( modelAssociation.isManyMultiplicity() )
2210         {
2211             // Map/Properties don't have bidi association, they can be directly set
2212             if ( modelAssociation.getType().equals( ModelDefault.PROPERTIES )
2213                             || modelAssociation.getType().equals( ModelDefault.MAP ) )
2214             {
2215                 if ( createSetBuilderFieldToInstance( ctorArgs, modelAssociation, sc ) )
2216                 {
2217                     return;
2218                 }
2219             }
2220 
2221             // check if there's no bidi association, so
2222 
2223             JavaAssociationMetadata javaAssociationMetadata = getJavaAssociationMetadata( modelAssociation );
2224 
2225             boolean bidirectionalAssociation = isBidirectionalAssociation( modelAssociation );
2226 
2227             if ( !bidirectionalAssociation || !javaAssociationMetadata.isBidi() )
2228             {
2229                 JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) modelAssociation.getMetadata( JavaFieldMetadata.ID );
2230 
2231                 // just use the plain old setter
2232                 if ( createSetBuilderFieldToInstance( ctorArgs, modelAssociation, sc ) )
2233                 {
2234                     return;
2235                 }
2236                 // or we can try to set by using the addAll if there is a getter available
2237                 else if ( javaFieldMetadata.isGetter() )
2238                 {
2239                     String action = isMap( modelAssociation.getType() ) ? "put" : "add";
2240 
2241                     sc.add( "instance.get" + capitalise( modelAssociation.getName() ) + "()." + action + "All( " + modelAssociation.getName() + " );" );
2242                     return;
2243                 }
2244             }
2245 
2246             // no previous precondition satisfied
2247             // or no one of the previous method worked
2248             // use the adder
2249             // bidi association can be handled directly by the model, not a Builder task
2250 
2251             String itemType;
2252             String targetField = modelAssociation.getName();
2253 
2254             if ( StringUtils.isNotEmpty( javaAssociationMetadata.getInterfaceName() ) )
2255             {
2256                 itemType = javaAssociationMetadata.getInterfaceName();
2257             }
2258             else if ( modelAssociation.getToClass() != null )
2259             {
2260                 itemType = modelAssociation.getToClass().getName();
2261             }
2262             else if ( modelAssociation.getType().equals( ModelDefault.PROPERTIES )
2263                       || modelAssociation.getType().equals( ModelDefault.MAP ) )
2264             {
2265                 StringBuilder itemTypeBuilder = new StringBuilder( "java.util.Map.Entry" );
2266 
2267                 if ( useJava5 )
2268                 {
2269                     itemTypeBuilder.append( '<' );
2270 
2271                     if ( modelAssociation.getType().equals( ModelDefault.PROPERTIES ) )
2272                     {
2273                         itemTypeBuilder.append( "Object, Object" );
2274                     }
2275                     else
2276                     {
2277                         itemTypeBuilder.append( "String, " ).append( modelAssociation.getTo() );
2278                     }
2279 
2280                     itemTypeBuilder.append( '>' );
2281                 }
2282 
2283                 itemType = itemTypeBuilder.toString();
2284 
2285                 targetField += ".entrySet()";
2286             }
2287             else
2288             {
2289                 itemType = "String";
2290             }
2291 
2292             if ( useJava5 )
2293             {
2294                 sc.add( "for ( " + itemType + " item : " + targetField + " )" );
2295             }
2296             else
2297             {
2298                 sc.add( "for ( java.util.Iterator it = " + targetField + ".iterator(); it.hasNext(); )" );
2299             }
2300 
2301             sc.add( "{" );
2302             sc.indent();
2303 
2304             if ( !useJava5 )
2305             {
2306                 sc.add( itemType + " item = (" + itemType + ") it.next();" );
2307             }
2308 
2309             StringBuilder adder = new StringBuilder( "instance.add" )
2310                         .append( capitalise( singular( modelAssociation.getName() ) ) )
2311                         .append( "( " );
2312 
2313             if ( modelAssociation.getType().equals( ModelDefault.PROPERTIES )
2314                  || modelAssociation.getType().equals( ModelDefault.MAP ) )
2315             {
2316                 appendEntryMethod( "String", "getKey()", adder, modelAssociation );
2317                 adder.append( ", " );
2318                 appendEntryMethod( modelAssociation.getTo(), "getValue()", adder, modelAssociation );
2319             }
2320             else
2321             {
2322                 adder.append( "item" );
2323             }
2324 
2325             adder.append( " );" );
2326 
2327             sc.add( adder.toString() );
2328 
2329             sc.unindent();
2330             sc.add( "}" );
2331         }
2332         else
2333         {
2334             createSetBuilderFieldToInstance( ctorArgs, modelAssociation, sc );
2335         }
2336     }
2337 
2338     private void appendEntryMethod( String type, String method, StringBuilder target, ModelAssociation modelAssociation )
2339     {
2340         if ( !useJava5 || modelAssociation.getType().equals( ModelDefault.PROPERTIES ) )
2341         {
2342             target.append( '(' ).append( type ).append( ") " );
2343         }
2344 
2345         target.append( "item." ).append( method );
2346     }
2347 
2348     private void generateStaticCreator( ModelClass modelClass, JClass jClass, JConstructor constructor )
2349         throws ModelloException
2350     {
2351         JMethod creatorMethod = new JMethod( "new" + modelClass.getName() + "Instance",
2352                                              new JClass( modelClass.getName() ),
2353                                              "a new <code>" + modelClass.getName() + "</code> instance." );
2354         creatorMethod.getModifiers().setStatic( true );
2355         creatorMethod.getJDocComment().setComment( "Creates a new <code>" + modelClass.getName() + "</code> instance." );
2356 
2357         ModelClass reference = modelClass;
2358 
2359         boolean hasDefaults = false;
2360 
2361         // traverse the whole modelClass hierarchy to create the static creator method
2362         while ( reference != null )
2363         {
2364             for ( ModelField modelField : reference.getFields( getGeneratedVersion() ) )
2365             {
2366                 // this is hacky
2367                 JField field = createField( modelField );
2368                 creatorMethod.addParameter( new JParameter( field.getType(), field.getName() ) );
2369 
2370                 if ( !StringUtils.isEmpty( modelField.getDefaultValue() ) )
2371                 {
2372                     hasDefaults = true;
2373                 }
2374             }
2375 
2376             if ( reference.hasSuperClass() )
2377             {
2378                 reference = reference.getModel().getClass( reference.getSuperClass(), getGeneratedVersion() );
2379             }
2380             else
2381             {
2382                 reference = null;
2383             }
2384         }
2385 
2386         JSourceCode sc = creatorMethod.getSourceCode();
2387 
2388         createInstanceAndSetProperties( modelClass, constructor, sc );
2389 
2390         jClass.addMethod( creatorMethod );
2391 
2392         // creates a shortcut with default values only if necessary
2393         if ( !hasDefaults )
2394         {
2395             return;
2396         }
2397 
2398         creatorMethod = new JMethod( "new" + modelClass.getName() + "Instance",
2399                                      new JClass( modelClass.getName() ),
2400                                      "a new <code>" + modelClass.getName() + "</code> instance." );
2401         creatorMethod.getModifiers().setStatic( true );
2402         creatorMethod.getJDocComment().setComment( "Creates a new <code>" + modelClass.getName() + "</code> instance." );
2403 
2404         StringBuilder shortcutArgs = new StringBuilder();
2405 
2406         reference = modelClass;
2407 
2408         while ( reference != null )
2409         {
2410             for ( ModelField modelField : reference.getFields( getGeneratedVersion() ) )
2411             {
2412                 if ( shortcutArgs.length() > 0 )
2413                 {
2414                     shortcutArgs.append( ',' );
2415                 }
2416 
2417                 shortcutArgs.append( ' ' );
2418 
2419                 if ( StringUtils.isEmpty( modelField.getDefaultValue() ) )
2420                 {
2421                     // this is hacky
2422                     JField field = createField( modelField );
2423                     creatorMethod.addParameter( new JParameter( field.getType(), field.getName() ) );
2424 
2425                     shortcutArgs.append( modelField.getName() );
2426                 }
2427                 else
2428                 {
2429                     shortcutArgs.append( getJavaDefaultValue( modelField ) );
2430                 }
2431 
2432                 shortcutArgs.append( ' ' );
2433             }
2434 
2435             if ( reference.hasSuperClass() )
2436             {
2437                 reference = reference.getModel().getClass( reference.getSuperClass(), getGeneratedVersion() );
2438             }
2439             else
2440             {
2441                 reference = null;
2442             }
2443         }
2444 
2445         sc = creatorMethod.getSourceCode();
2446 
2447         sc.add( "return new" + modelClass.getName() + "Instance(" + shortcutArgs + ");" );
2448 
2449         jClass.addMethod( creatorMethod );
2450     }
2451 
2452 }