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