View Javadoc
1   package xuml.tools.model.compiler;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.PrintStream;
5   import java.io.Serializable;
6   import java.lang.reflect.InvocationTargetException;
7   import java.math.BigDecimal;
8   import java.math.BigInteger;
9   import java.util.Collection;
10  import java.util.Date;
11  import java.util.HashSet;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.concurrent.TimeUnit;
16  import java.util.regex.Pattern;
17  
18  import javax.persistence.Basic;
19  import javax.persistence.CascadeType;
20  import javax.persistence.Column;
21  import javax.persistence.DiscriminatorColumn;
22  import javax.persistence.DiscriminatorType;
23  import javax.persistence.DiscriminatorValue;
24  import javax.persistence.Embeddable;
25  import javax.persistence.EmbeddedId;
26  import javax.persistence.Entity;
27  import javax.persistence.EntityManager;
28  import javax.persistence.FetchType;
29  import javax.persistence.GeneratedValue;
30  import javax.persistence.GenerationType;
31  import javax.persistence.Id;
32  import javax.persistence.Inheritance;
33  import javax.persistence.InheritanceType;
34  import javax.persistence.JoinColumn;
35  import javax.persistence.JoinColumns;
36  import javax.persistence.JoinTable;
37  import javax.persistence.Lob;
38  import javax.persistence.ManyToMany;
39  import javax.persistence.ManyToOne;
40  import javax.persistence.OneToMany;
41  import javax.persistence.OneToOne;
42  import javax.persistence.PrePersist;
43  import javax.persistence.PreUpdate;
44  import javax.persistence.Table;
45  import javax.persistence.Temporal;
46  import javax.persistence.TemporalType;
47  import javax.persistence.Transient;
48  import javax.persistence.UniqueConstraint;
49  
50  import org.apache.commons.lang.StringEscapeUtils;
51  
52  import com.google.common.base.MoreObjects;
53  import com.google.common.base.Objects;
54  import com.google.common.base.Optional;
55  import com.google.common.base.Preconditions;
56  import com.google.common.collect.Lists;
57  import com.google.common.collect.Maps;
58  import com.google.common.collect.Sets;
59  
60  import scala.concurrent.duration.Duration;
61  import xuml.tools.model.compiler.ClassInfo.OtherId;
62  import xuml.tools.model.compiler.info.Mult;
63  import xuml.tools.model.compiler.info.MyAttributeExtensions;
64  import xuml.tools.model.compiler.info.MyEvent;
65  import xuml.tools.model.compiler.info.MyFind;
66  import xuml.tools.model.compiler.info.MyIdAttribute;
67  import xuml.tools.model.compiler.info.MyIndependentAttribute;
68  import xuml.tools.model.compiler.info.MyJoinColumn;
69  import xuml.tools.model.compiler.info.MyJoinTable;
70  import xuml.tools.model.compiler.info.MyParameter;
71  import xuml.tools.model.compiler.info.MyReferenceMember;
72  import xuml.tools.model.compiler.info.MySpecializations;
73  import xuml.tools.model.compiler.info.MySubclassRole;
74  import xuml.tools.model.compiler.info.MyTransition;
75  import xuml.tools.model.compiler.info.MyType;
76  import xuml.tools.model.compiler.info.MyTypeDefinition;
77  import xuml.tools.model.compiler.runtime.BehaviourFactoryNotSetException;
78  import xuml.tools.model.compiler.runtime.CreationEvent;
79  import xuml.tools.model.compiler.runtime.EntityHelper;
80  import xuml.tools.model.compiler.runtime.Event;
81  import xuml.tools.model.compiler.runtime.RelationshipNotEstablishedException;
82  import xuml.tools.model.compiler.runtime.Signaller;
83  import xuml.tools.model.compiler.runtime.TooManySpecializationsException;
84  import xuml.tools.model.compiler.runtime.ValidationException;
85  import xuml.tools.model.compiler.runtime.query.BooleanExpression;
86  import xuml.tools.model.compiler.runtime.query.Field;
87  import xuml.tools.model.compiler.runtime.query.NumericExpressionField;
88  import xuml.tools.model.compiler.runtime.query.SelectBuilder;
89  import xuml.tools.model.compiler.runtime.query.StringExpressionField;
90  
91  public class ClassWriter {
92  
93      private static final String BEHAVIOUR_COMMENT = "All actions like onEntry actions and defined\noperations are performed by this Behaviour class.";
94      private static final String STATE_COMMENT = "For internal use only by the state machine but is persisted by the jpa provider.";
95      private static final String MEMBER_MODIFIERS = "private";
96      private static final int MAX_VARCHAR_LENGTH = 65535;
97      public static boolean useJpaJoinedStrategyForSpecialization = false;
98      private final ClassInfo info;
99  
100     public ClassWriter(ClassInfo info) {
101         this.info = info;
102     }
103 
104     public String generate() {
105         Set<String> validationMethods = Sets.newTreeSet();
106         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
107         PrintStream out = new PrintStream(bytes);
108         writeClassJavadoc(out, info);
109         writeClassAnnotation(out, info);
110         writeClassDeclaration(out, info);
111         writeConstructors(out, info);
112         writeEntityHelper(out, info);
113         writeIdMember(out, info, validationMethods);
114         writeUniqueIdMethod(out, info);
115         writeNonIdIndependentAttributeMembers(out, info, validationMethods);
116         writeStateMember(out, info);
117         writeReferenceMembers(out, info, validationMethods);
118         writeSuperclassValidationCheck(out, info, validationMethods);
119         writePreUpdateCheck(out, info, validationMethods);
120         writeIdGetterAndSetter(out, info);
121         writeNonIdIndependentAttributeGettersAndSetters(out, info);
122         writeStateGetterAndSetter(out, info);
123         writeStates(out, info);
124         writeEvents(out, info);
125         writeSignalMethods(out, info);
126         writeStaticCreateMethods(out, info);
127         writeMergeMethod(out, info);
128         writePersistMethod(out, info);
129         writeRemoveMethod(out, info);
130         writeRefreshMethod(out, info);
131         writeLoadMethod(out, info);
132         writeToStringMethod(out, info);
133         writeBehaviourInterface(out, info);
134         writeBehaviourFactoryInterface(out, info);
135         writeBehaviourFactoryCreator(out, info);
136         writeStaticFinderMethods(out, info);
137 
138         writeQueryMethods(out, info);
139 
140         writeClassClose(out);
141         ByteArrayOutputStream headerBytes = new ByteArrayOutputStream();
142         PrintStream header = new PrintStream(headerBytes);
143         writePackage(header, info);
144         writeImports(header, info);
145         out.close();
146         header.close();
147         return headerBytes.toString() + bytes.toString();
148     }
149 
150     private void writeSuperclassValidationCheck(PrintStream out, ClassInfo info,
151             Set<String> validationMethods) {
152         if (info.isSuperclass()) {
153             List<MySpecializations> list = info.getSpecializations();
154             for (MySpecializations sp : list) {
155                 String methodName = writeSpecializationValidationMethod(out, info, sp);
156                 validationMethods.add(methodName);
157             }
158         }
159     }
160 
161     private String writeSpecializationValidationMethod(PrintStream out, ClassInfo info,
162             MySpecializations sp) {
163         String methodName = "validateSpecializationR" + sp.getRnum();
164         out.format("    private void %s() {\n", methodName);
165         out.format("        int count = 0;\n");
166         for (String fieldName : sp.getFieldNames()) {
167             out.format("        if (%s != null)\n", fieldName);
168             out.format("            count++;\n");
169         }
170         out.format("        if (count == 0)\n");
171         out.format("            throw new %s(\"wrong number of specializations = \" + count);\n",
172                 info.addType(RelationshipNotEstablishedException.class));
173         out.format("        if (count != 1)\n");
174         out.format("            throw new %s(\"wrong number of specializations = \" + count);\n",
175                 info.addType(TooManySpecializationsException.class));
176         out.format("        }\n\n");
177         return methodName;
178     }
179 
180     private void writeClassJavadoc(PrintStream out, ClassInfo info) {
181         jd(out, info.getClassDescription(), "");
182     }
183 
184     private void writeClassAnnotation(PrintStream out, ClassInfo info) {
185         out.format("@%s\n", info.addType(Entity.class));
186         List<List<String>> uniqueConstraints = info.getUniqueConstraintColumnNames();
187         if (uniqueConstraints.size() >= 1) {
188             out.format("@%s(schema=\"%s\", name=\"%s\",\n", info.addType(Table.class),
189                     info.getSchema(), info.getTable());
190             StringBuilder s = new StringBuilder();
191             for (List<String> list : uniqueConstraints) {
192                 if (s.length() > 0)
193                     s.append(",\n");
194                 s.append("        @" + info.addType(UniqueConstraint.class) + "(columnNames={"
195                         + getCommaDelimitedQuoted(list) + "})");
196             }
197             out.format("    uniqueConstraints={\n");
198             out.format("%s})\n", s);
199         } else {
200             out.format("@%s(schema=\"%s\", name=\"%s\")\n", info.addType(Table.class),
201                     info.getSchema(), info.getTable());
202         }
203 
204         if (useJpaJoinedStrategyForSpecialization)
205             writeJpaInheritanceAnnotations(out, info);
206     }
207 
208     private void writeJpaInheritanceAnnotations(PrintStream out, ClassInfo info) {
209 
210         if (info.isSuperclass()) {
211             out.format("@%s(strategy = %s.JOINED)\n", info.addType(Inheritance.class),
212                     info.addType(InheritanceType.class));
213             out.format(
214                     "//DiscriminatorColumn annotation is ignored by Hibernate but may be used\n");
215             out.format(
216                     "//by other JPA providers. See https://hibernate.onjira.com/browse/ANN-140\n");
217             out.format(
218                     "@%s(name = \"DISCRIMINATOR\", discriminatorType = %s.STRING, length = 255)\n",
219                     info.addType(DiscriminatorColumn.class), info.addType(DiscriminatorType.class));
220         }
221         if (info.isSubclass()) {
222             MySubclassRole subclass = info.getSubclassRole();
223             out.format("@%s(\"%s\")\n", info.addType(DiscriminatorValue.class),
224                     subclass.getDiscriminatorValue());
225         }
226     }
227 
228     private void writeClassDeclaration(PrintStream out, ClassInfo info) {
229         String extension;
230         if (useJpaJoinedStrategyForSpecialization && info.isSubclass()) {
231             MySubclassRole subclass = info.getSubclassRole();
232             extension = " extends " + info.addType(subclass.getSuperclassJavaFullClassName());
233         } else
234             extension = "";
235 
236         out.format("public class %s%s implements %s<%1$s> {\n\n", info.getJavaClassSimpleName(),
237                 extension, info.addType(xuml.tools.model.compiler.runtime.Entity.class));
238     }
239 
240     private Type getIdType(ClassInfo info) {
241         Type idType;
242         if (hasEmbeddedId())
243             idType = new Type(info.getPackage() + "." + info.getJavaClassSimpleName() + "."
244                     + info.getEmbeddedIdSimpleClassName());
245         else
246             idType = info.getPrimaryIdAttributeMembers().get(0).getType().getType();
247         return idType;
248     }
249 
250     private void writeConstructors(PrintStream out, ClassInfo info) {
251         // constructor
252         jd(out, "No argument constructor required by JPA.", "    ");
253         out.format("    public %s(){\n", info.getJavaClassSimpleName());
254         out.format("        //JPA requires no-arg constructor\n");
255         if (info.hasBehaviour()) {
256             out.format("        if (_behaviourFactory == null) {\n");
257             out.format(
258                     "            throw new %s(\"%s does not have "
259                             + "a BehaviourFactory set. Use %s.setBehaviourFactory in your App class (one-time setup).\");\n",
260                     info.addType(BehaviourFactoryNotSetException.class),
261                     info.getJavaClassSimpleName(), info.getJavaClassSimpleName());
262             out.format("        }\n");
263             out.format("        _behaviour = _behaviourFactory.create(this);\n");
264         }
265         out.format("    }\n\n");
266         if (info.hasBehaviour()) {
267             String factoryTypeName = "BehaviourFactory";
268             String behaviourTypeName = "Behaviour";
269 
270             writeBehaviourFields(out, info, factoryTypeName, behaviourTypeName);
271 
272             writeBehaviourFactoryGetterAndSetter(out, factoryTypeName);
273         }
274 
275         String idClassName = getIdClassName(info);
276 
277         writeConstructorUsingId(out, info, idClassName);
278 
279         writeCreatorUsingId(out, info, idClassName);
280 
281         writeCreatorUsingCreationEvent(out, info);
282 
283     }
284 
285     private String getIdClassName(ClassInfo info) {
286         String idClassName;
287         if (!hasEmbeddedId()) {
288             if (info.getPrimaryIdAttributeMembers().size() == 0) {
289                 throw new RuntimeException(
290                         "Class does not have identifier, inherited or otherwise: "
291                                 + info.getJavaClassSimpleName());
292             }
293 
294             idClassName = info
295                     .addType(info.getPrimaryIdAttributeMembers().get(0).getType().getType());
296         } else
297             idClassName = info.getEmbeddedIdSimpleClassName();
298         return idClassName;
299     }
300 
301     private void writeConstructorUsingId(PrintStream out, ClassInfo info, String idClassName) {
302         // constructor using Id
303         jd(out, "Constructor using id.", "    ");
304         out.format("    public %s(%s id) {\n", info.getJavaClassSimpleName(), idClassName);
305         out.format("        this.id = id;\n");
306         out.format("    }\n\n");
307     }
308 
309     private void writeCreatorUsingId(PrintStream out, ClassInfo info, String idClassName) {
310         // static creator using Id
311         jd(out, "Static creator method using id.", "    ");
312         out.format("    public static %s create(%s id) {\n", info.getJavaClassSimpleName(),
313                 idClassName);
314         out.format("        return new %s(id);\n", info.getJavaClassSimpleName());
315         out.format("    }\n\n");
316     }
317 
318     private void writeCreatorUsingCreationEvent(PrintStream out, ClassInfo info) {
319         // static creator using Id
320         jd(out, "Static creator method using CreationEvent.", "    ");
321         out.format("    public static %s create(%s<%s> creationEvent) {\n",
322                 info.getJavaClassSimpleName(), info.addType(CreationEvent.class),
323                 info.getJavaClassSimpleName());
324         out.format("        return Context.create(%s.class, creationEvent);\n",
325                 info.getJavaClassSimpleName());
326         out.format("    }\n\n");
327     }
328 
329     private void writeBehaviourFields(PrintStream out, ClassInfo info, String factoryTypeName,
330             String behaviourTypeName) {
331         jd(out, "If behaviour is not explicitly specified then the\n"
332                 + "behaviour factory is used to create behaviour.", "    ");
333         out.format("    private static volatile %s _behaviourFactory;\n\n", factoryTypeName);
334 
335         jd(out, BEHAVIOUR_COMMENT, "    ");
336         out.format("    @%s\n", info.addType(Transient.class));
337         out.format("    private %s _behaviour;\n\n", behaviourTypeName);
338     }
339 
340     private void writeConstructorUsingBehaviour(PrintStream out, ClassInfo info,
341             String behaviourTypeName) {
342         jd(out, "Constructor using Behaviour.", "    ");
343         if (info.useGuiceInjection())
344             out.format("    @%s\n", info.addType("com.google.inject.Inject"));
345         out.format("    public %s(%s behaviour){\n", info.getJavaClassSimpleName(),
346                 behaviourTypeName);
347         out.format("        %s.checkNotNull(_behaviourFactory,\n",
348                 info.addType(Preconditions.class));
349         out.format(
350                 "            \"You need to call static method setBehaviourFactory before instantiating \" + %s.class.getName());\n",
351                 info.getJavaClassSimpleName());
352         out.format("        this._behaviour = behaviour;\n");
353         out.format("    }\n\n");
354     }
355 
356     private void writeBehaviourFactoryGetterAndSetter(PrintStream out, String factoryTypeName) {
357         jd(out, "Sets the BehaviourFactory for all instances of\n"
358                 + "this class. It will only be used when Behaviour\n"
359                 + "is not explicitly provided in the constructor.", "    ");
360         out.format("    public static void setBehaviourFactory(%s factory){\n", factoryTypeName);
361         out.format("        _behaviourFactory = factory;\n");
362         out.format("    }\n\n");
363 
364         jd(out, "Sets the BehaviourFactory for all instances of\n"
365                 + "this class using the given Behaviour class as the base. It will only be used when Behaviour\n"
366                 + "is not explicitly provided in the constructor.", "    ");
367         out.format("    public static void setBehaviourFactory(%s<? extends Behaviour> cls){\n",
368                 info.addType(Class.class));
369         out.format("        _behaviourFactory = createBehaviourFactory(cls);\n");
370         out.format("    }\n\n");
371 
372         jd(out, "Returns the singleton BehaviourFactory for this.", "    ");
373         out.format("    public static %s getBehaviourFactory(){\n", factoryTypeName);
374         out.format("        return _behaviourFactory;\n");
375         out.format("    }\n\n");
376     }
377 
378     private boolean hasEmbeddedId() {
379         return info.hasCompositeId();
380     }
381 
382     private void writeEntityHelper(PrintStream out, ClassInfo info) {
383         jd(out, "The signaller used by the current Context. It will\nget injected into the EntityHelper.",
384                 "    ");
385         out.format("    private static %s signaller;\n\n", info.addType(Signaller.class));
386 
387         jd(out, "Sets the Signaller to be used by the EntityHelper.", "    ");
388         out.format("    static void setSignaller_(%s sig) {\n", info.addType(Signaller.class));
389         out.format("        signaller = sig;\n");
390         out.format("    }\n\n");
391 
392         jd(out, "Helper for this class.", "    ");
393         out.format("    @%s\n", info.addType(Transient.class));
394         out.format("    private %s _helper;\n\n", info.addType(EntityHelper.class));
395 
396         jd(out, "Returns the Helper for this instance.", "    ");
397         out.format("    public synchronized %s helper() {\n", info.addType(EntityHelper.class));
398         out.format("        if (_helper==null)\n");
399         out.format("            _helper = new %s(signaller,this);\n",
400                 info.addType(EntityHelper.class));
401         out.format("        return _helper;\n");
402         out.format("    }\n\n");
403     }
404 
405     private void writeIdMember(PrintStream out, ClassInfo info, Set<String> validationMethods) {
406         if (!hasEmbeddedId()) {
407             writeSimpleIdMember(out, info);
408         } else {
409             writeEmbeddedIdMember(out, info);
410 
411             writeEmbeddedIdDeclaration(out, info);
412 
413             writeEmbeddedIdConstructor(out, info);
414 
415             writeEmbeddedIdFields(out, info, validationMethods);
416 
417             writeEmbeddedIdGettersAndSetters(out, info);
418 
419             writeEmbeddedIdToString(out, info);
420 
421             writeEmbeddedIdEquals(out, info);
422 
423             writeEmbeddedIdHashCode(out, info);
424 
425             writeEmbeddedIdBuilder(out, info);
426 
427             out.format("    }\n\n");
428         }
429     }
430 
431     private void writeSimpleIdMember(PrintStream out, ClassInfo info) {
432         jd(out, "Primary identifier", "    ");
433         out.format("    @%s\n", info.addType(Id.class));
434         MyIdAttribute attribute = info.getPrimaryIdAttributeMembers().get(0);
435         // override attribute field name to 'id'
436         writeIndependentAttributeMember(out, "id", attribute.getColumnName(), false, "    ",
437                 attribute.getType(), attribute.getExtensions());
438     }
439 
440     private void writeEmbeddedIdEquals(PrintStream out, ClassInfo info) {
441         out.format("        @%s\n", info.addType(Override.class));
442         out.format("        public boolean equals(Object obj) {\n");
443         out.format("            if (obj==null)\n");
444         out.format("                return false;\n");
445         out.format("            if (getClass() != obj.getClass())\n");
446         out.format("                return false;\n");
447         out.format("            final %1$s other = (%1$s) obj;\n",
448                 info.getEmbeddedIdSimpleClassName());
449         out.format("            return ");
450         boolean first = true;
451         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
452             if (!first) {
453                 out.println();
454                 out.format("                && ");
455             }
456             out.format("%s.equal(this.%2$s, other.%2$s)", info.addType(Objects.class),
457                     member.getFieldName());
458             first = false;
459         }
460         out.format(";\n");
461 
462         out.format("        }\n\n");
463     }
464 
465     private void writeEmbeddedIdHashCode(PrintStream out, ClassInfo info) {
466         out.format("        @%s\n", info.addType(Override.class));
467         out.format("        public int hashCode() {\n");
468         out.format("            return %s.hashCode(\n", info.addType(Objects.class));
469         boolean first = true;
470         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
471             if (!first) {
472                 out.format(",\n");
473             }
474             out.format("                ");
475             out.format("this.%s", member.getFieldName());
476             first = false;
477         }
478         out.format(");\n");
479         out.format("        }\n\n");
480     }
481 
482     private void writeEmbeddedIdBuilder(PrintStream out, ClassInfo info) {
483         out.format("        public static Builder builder() {\n");
484         out.format("            return new Builder();\n");
485         out.format("        }\n\n");
486         out.format("        public %s(Builder builder) {\n", info.getEmbeddedIdSimpleClassName());
487         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
488             out.format("            this.%s = builder.%s;\n", member.getFieldName(),
489                     member.getFieldName());
490         }
491         out.format("        }\n\n");
492         out.format("        public static class Builder {\n\n");
493 
494         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
495             out.format("            private %s %s;\n", info.addType(member.getType().getType()),
496                     member.getFieldName());
497         }
498         out.println();
499         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
500             out.format("            public Builder %s(%s %s) {\n", member.getFieldName(),
501                     info.addType(member.getType().getType()), member.getFieldName());
502             out.format("                this.%s = %s;\n", member.getFieldName(),
503                     member.getFieldName());
504             out.format("                return this;\n");
505             out.format("            }\n\n");
506         }
507         out.format("            public %s build() {\n", info.getEmbeddedIdSimpleClassName());
508         out.format("                return new %s(this);\n", info.getEmbeddedIdSimpleClassName());
509         out.format("            }\n\n");
510         out.format("        }\n\n");
511 
512     }
513 
514     private void writeEmbeddedIdDeclaration(PrintStream out, ClassInfo info) {
515         out.format("    @%s\n", info.addType(Embeddable.class));
516         out.format("    @%s(\"serial\")\n", info.addType(SuppressWarnings.class));
517         out.format("    public static class %s implements %s {\n\n",
518                 info.getEmbeddedIdSimpleClassName(), info.addType(Serializable.class));
519         out.format("        public %s() {\n", info.getEmbeddedIdSimpleClassName());
520         out.format("            //JPA requires no-arg constructor\n");
521         out.format("        }\n\n");
522     }
523 
524     private void writeEmbeddedIdToString(PrintStream out, ClassInfo info) {
525         out.format("%s@%s\n", "        ", info.addType(Override.class));
526         out.format("%spublic %s toString(){\n", "        ", info.addType(String.class));
527         out.format("%s%s _s = new %s();\n", "            ", info.addType(StringBuffer.class),
528                 info.addType(StringBuffer.class));
529         out.format("            _s.append(\"%s [\");\n", info.getEmbeddedIdSimpleClassName());
530         boolean first = true;
531         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
532             if (!first)
533                 out.format("%s_s.append(\",\");\n", "            ");
534             out.format("%s_s.append(\"%s=\");\n", "            ", member.getFieldName());
535             out.format("%s_s.append(%s.toString());\n", "            ", member.getFieldName());
536             first = false;
537         }
538         out.format("            _s.append(\"]\");\n");
539         out.format("%sreturn _s.toString();\n", "            ");
540         out.format("%s}\n\n", "        ");
541     }
542 
543     private void writeEmbeddedIdGettersAndSetters(PrintStream out, ClassInfo info) {
544         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
545             jd(out, "Returns the value of the attribute '" + member.getAttributeName() + "'",
546                     "        ");
547             out.format("%spublic %s get%s(){\n", "        ",
548                     info.addType(member.getType().getType()),
549                     Util.upperFirst(member.getFieldName()));
550             out.format("%sreturn %s;\n", "            ", member.getFieldName());
551             out.format("%s}\n\n", "        ");
552 
553             jd(out, "Sets the value of attribute '" + member.getAttributeName() + "'", "        ");
554             out.format("%spublic void set%s(%s %s){\n", "        ",
555                     Util.upperFirst(member.getFieldName()),
556                     info.addType(member.getType().getType()), member.getFieldName());
557             out.format("%sthis.%s = %s;\n", "            ", member.getFieldName(),
558                     member.getFieldName());
559             out.format("%s}\n\n", "        ");
560         }
561     }
562 
563     private void writeEmbeddedIdMember(PrintStream out, ClassInfo info) {
564         jd(out, "Id field.", "    ");
565         out.format("    @%s\n", info.addType(EmbeddedId.class));
566         out.format("    %s %s %s;\n\n", MEMBER_MODIFIERS, info.getEmbeddedIdSimpleClassName(),
567                 info.getEmbeddedIdAttributeName());
568     }
569 
570     private void writeEmbeddedIdFields(PrintStream out, ClassInfo info,
571             Set<String> validationMethods) {
572         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
573             jd(out, "Field for attribute '" + member.getAttributeName() + "'.", "        ");
574             if (member.getReferenceClass() == null) {
575                 writeFieldAnnotation(out, member.getColumnName(), false, "        ",
576                         member.getType(), true, true);
577             } else {
578                 writeFieldAnnotation(out, member.getColumnName(), false, "        ",
579                         member.getType(), false, false);
580             }
581             writeField(out, info, member.getType(), member.getFieldName(), "        ");
582 
583             writeAttributeValidationMethod(out, member.getFieldName(), member.getType(), info,
584                     validationMethods, true, member.getExtensions().isGenerated());
585         }
586     }
587 
588     private void writeField(PrintStream out, ClassInfo info, MyTypeDefinition type,
589             String fieldName, String indent) {
590         String defaultValue = type.getDefaultValue();
591         if (defaultValue == null)
592             out.format("%s%s %s %s;\n\n", indent, MEMBER_MODIFIERS, info.addType(type.getType()),
593                     fieldName);
594         else if (type.getType().getBase().equals(Date.class.getName()))
595             out.format("%s%s %s %s = new %s(%s);\n\n", indent, MEMBER_MODIFIERS,
596                     info.addType(type.getType()), fieldName, info.addType(type.getType()),
597                     defaultValue);
598         else if (type.getType().getBase().equals(byte.class.getName()))
599             out.format("%s%s %s %s = new byte[0];\n\n", indent, MEMBER_MODIFIERS,
600                     info.addType(type.getType()), fieldName, info.addType(type.getType()),
601                     defaultValue);
602         else
603             out.format("%s%s %s %s = new %s(\"%s\");\n\n", indent, MEMBER_MODIFIERS,
604                     info.addType(type.getType()), fieldName, info.addType(type.getType()),
605                     defaultValue);
606     }
607 
608     private void writeEmbeddedIdConstructor(PrintStream out, ClassInfo info) {
609         // write constructor
610         jd(out, "Primary identifier constructor.", "        ");
611         out.format("        public %s(", info.getEmbeddedIdSimpleClassName());
612         boolean first = true;
613         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
614             if (!first)
615                 out.format(", ");
616             out.format("%s %s", info.addType(member.getType().getType()), member.getFieldName());
617             first = false;
618         }
619         out.format(") {\n");
620         first = true;
621         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
622             out.format("            this.%s = %s;\n", member.getFieldName(), member.getFieldName());
623             first = false;
624         }
625         out.format("        }\n\n");
626     }
627 
628     private void writeUniqueIdMethod(PrintStream out, ClassInfo info) {
629         jd(out, "Returns a unique id for this instance as a String. \nUsed for synchronizing access to entities.",
630                 "    ");
631         out.format("    @%s\n", info.addType(Override.class));
632         out.format("    public String uniqueId(){\n");
633         out.format("        return %s.class.getName() + \":\" + getId();\n",
634                 info.getJavaClassSimpleName());
635         out.format("    }\n\n");
636 
637     }
638 
639     private void writeNonIdIndependentAttributeMembers(PrintStream out, ClassInfo info,
640             Set<String> validationMethods) {
641         for (MyIndependentAttribute attribute : info.getNonPrimaryIdIndependentAttributeMembers()) {
642             writeIndependentAttributeMember(out, attribute, "    ");
643             writeAttributeValidationMethod(out, attribute, info, validationMethods);
644         }
645     }
646 
647     private void writeAttributeValidationMethod(PrintStream out, MyIndependentAttribute attribute,
648             ClassInfo info, Set<String> validationMethods) {
649         writeAttributeValidationMethod(out, attribute.getFieldName(), attribute.getType(), info,
650                 validationMethods, false, attribute.getExtensions().isGenerated());
651     }
652 
653     private void writeAttributeValidationMethod(PrintStream out, String fieldName,
654             MyTypeDefinition type, ClassInfo info, Set<String> validationMethods,
655             boolean inEmbeddedId, boolean generated) {
656 
657         if (generated)
658             return;
659         String indent;
660         if (inEmbeddedId)
661             indent = "        ";
662         else
663             indent = "    ";
664         MyType myType = type.getMyType();
665         String attributeConstantIdentifier = Util.toJavaConstantIdentifier(fieldName);
666         if (myType.equals(MyType.REAL) || myType.equals(MyType.INTEGER)) {
667             out.format("%sprivate static final %s %s_UPPER_LIMIT=new BigDecimal(\"%s\");\n", indent,
668                     info.addType(BigDecimal.class), attributeConstantIdentifier,
669                     type.getUpperLimit());
670             out.format("%sprivate static final %s %s_LOWER_LIMIT=new BigDecimal(\"%s\");\n\n",
671                     indent, info.addType(BigDecimal.class), attributeConstantIdentifier,
672                     type.getLowerLimit());
673         }
674         String validationMethodName = "validate" + Util.upperFirst(fieldName);
675         if (inEmbeddedId)
676             validationMethods.add("id." + validationMethodName);
677         else
678             validationMethods.add(validationMethodName);
679         jd(out, "Validates " + fieldName + " against type constraints.", indent);
680         out.format("%sprivate void %s() {\n", indent, validationMethodName);
681         Class<? extends RuntimeException> ex = ValidationException.class;
682         if (myType.equals(MyType.REAL) || myType.equals(MyType.INTEGER)) {
683             out.format("%s    if (%s_UPPER_LIMIT.doubleValue() < %s) \n", indent,
684                     attributeConstantIdentifier, fieldName);
685             out.format("%s        throw new %s(\"upper limit of %s failed\");\n", indent,
686                     info.addType(ex), type.getUpperLimit().toString());
687             out.format("%s    if (%s_LOWER_LIMIT.doubleValue() > %s)\n", indent,
688                     attributeConstantIdentifier, fieldName);
689             out.format("%s         throw new %s(\"lower limit of %s failed\");\n", indent,
690                     info.addType(ex), type.getLowerLimit().toString());
691         } else if (myType.equals(MyType.STRING)) {
692             if (type.getMinLength().intValue() > 0) {
693                 out.format("%s    if (%s == null || %s.length() < %s)\n", indent, fieldName,
694                         fieldName, type.getMinLength().toString());
695                 out.format("%s        throw new %s(\"min length constraint not met\");\n", indent,
696                         info.addType(ex));
697             }
698             // max length already handles by JPA annotations on column
699             if (type.getPrefix() != null) {
700                 out.format("%s     if (%s == null || !%s.startsWith(\"%s\"))\n", indent, fieldName,
701                         fieldName, StringEscapeUtils.escapeJava(type.getPrefix()));
702                 out.format("%s        throw new %s(\"prefix constraint not met\");\n", indent,
703                         info.addType(ex));
704             }
705             if (type.getSuffix() != null) {
706                 out.format("%s    if (%s == null || !%s.endsWith(\"%s\"))\n", indent, fieldName,
707                         fieldName, StringEscapeUtils.escapeJava(type.getSuffix()));
708                 out.format("%s        throw new %s(\"suffix constraint not met\");\n", indent,
709                         info.addType(ex));
710             }
711             if (type.getValidationPattern() != null) {
712                 out.format("%s    if (%s == null || !%s.matches(\"%s\", %s))\n", indent, fieldName,
713                         info.addType(Pattern.class),
714                         StringEscapeUtils.escapeJava(type.getValidationPattern()), fieldName);
715                 out.format("%s        throw new %s(\"validation pattern constraint not met\");\n",
716                         indent, info.addType(ex));
717             }
718         } else if (myType.equals(MyType.BYTES)) {
719             if (type.getMinLength().intValue() > 0) {
720                 out.format("%s    if (%s == null || %s.length < %s)\n", indent, fieldName,
721                         fieldName, type.getMinLength().toString());
722                 out.format("%s        throw new %s(\"min length constraint not met\");\n", indent,
723                         info.addType(ex));
724             }
725             if (type.getMaxLength().intValue() > 0) {
726                 out.format("%s    if (%s == null || %s.length > %s)\n", indent, fieldName,
727                         fieldName, type.getMaxLength().toString());
728                 out.format("%s        throw new %s(\"max length constraint not met\");\n", indent,
729                         info.addType(ex));
730             }
731         }
732 
733         out.format("    }\n\n");
734     }
735 
736     private void writeStateMember(PrintStream out, ClassInfo info) {
737         if (info.hasBehaviour()) {
738             info.addType(Column.class);
739             jd(out, STATE_COMMENT, "    ");
740             out.format("    @%s(name=\"state\",nullable=false)\n", info.addType(Column.class));
741             out.format("    %s String state;\n\n", MEMBER_MODIFIERS);
742         }
743     }
744 
745     private void writeReferenceMembers(PrintStream out, ClassInfo info,
746             Set<String> validationMethods) {
747         for (MyReferenceMember ref : info.getReferenceMembers()) {
748             writeReferenceJavadoc(out, info, ref);
749             if (isRelationship(ref, Mult.ONE, Mult.ONE)) {
750                 writeReferenceMembersOneToOne(out, info, validationMethods, ref);
751             } else if (isRelationship(ref, Mult.ONE, Mult.ZERO_ONE)) {
752                 writeReferenceMembersOneToZeroOne(out, info, ref);
753             } else if (isRelationship(ref, Mult.ZERO_ONE, Mult.ONE)) {
754                 writeReferenceMembersZeroOneToOne(out, info, ref);
755             } else if (isRelationship(ref, Mult.ONE, Mult.MANY)) {
756                 writeReferenceMembersOneToMany(out, info, ref);
757             } else if (isRelationship(ref, Mult.MANY, Mult.ONE)) {
758                 writeReferenceMembersManyToOne(out, info, ref);
759             } else if (isRelationship(ref, Mult.ONE, Mult.ONE_MANY)) {
760                 writeReferenceMembersOneToOneMany(out, info, validationMethods, ref);
761             } else if (isRelationship(ref, Mult.ONE_MANY, Mult.ONE)) {
762                 writeReferenceMembersOneManyToOne(out, info, validationMethods, ref);
763             } else if (isRelationship(ref, Mult.ZERO_ONE, Mult.ZERO_ONE)) {
764                 writeReferenceMembersZeroOneToZeroOne(out, info, ref);
765             } else if (isRelationship(ref, Mult.ZERO_ONE, Mult.MANY)) {
766                 writeReferenceMembersZeroOneToMany(out, info, ref);
767             } else if (isRelationship(ref, Mult.MANY, Mult.ZERO_ONE)) {
768                 writeReferenceMembersManyToZeroOne(out, info, ref);
769             } else if (isRelationship(ref, Mult.ZERO_ONE, Mult.ONE_MANY)) {
770                 writeValidationNotEmpty(out, ref.getFieldName(), validationMethods);
771                 writeReferenceMembersZeroOneToMany(out, info, ref);
772             } else if (isRelationship(ref, Mult.ONE_MANY, Mult.ZERO_ONE)) {
773                 writeReferenceMembersManyToZeroOne(out, info, ref);
774             } else if (isRelationship(ref, Mult.MANY, Mult.MANY)) {
775                 writeManyToMany(out, info, ref);
776             } else if (isRelationship(ref, Mult.ONE_MANY, Mult.ONE_MANY)) {
777                 writeManyToMany(out, info, ref);
778             } else if (isRelationship(ref, Mult.ONE_MANY, Mult.MANY)) {
779                 writeManyToManySecondarySide(out, info, ref);
780             } else if (isRelationship(ref, Mult.MANY, Mult.ONE_MANY)) {
781                 writeManyToManyPrimarySide(out, info, ref);
782             }
783         }
784     }
785 
786     private void writeReferenceJavadoc(PrintStream out, ClassInfo info, MyReferenceMember ref) {
787         String javadoc = getReferenceJavadoc(info, ref);
788         jd(out, javadoc, "    ");
789     }
790 
791     private String getReferenceJavadoc(ClassInfo info, MyReferenceMember ref) {
792         return ref.getThisMult() + " " + info.getJavaClassSimpleName() + " "
793                 + ref.getThatVerbClause() + " " + ref.getThatMult() + " "
794                 + ref.getSimpleClassName();
795     }
796 
797     private void writeReferenceMembersOneToOneMany(PrintStream out, ClassInfo info,
798             Set<String> validationMethods, MyReferenceMember ref) {
799         writeValidationNotEmpty(out, ref.getFieldName(), validationMethods);
800         writeReferenceMembersOneToMany(out, info, ref);
801     }
802 
803     private void writeReferenceMembersManyToZeroOne(PrintStream out, ClassInfo info,
804             MyReferenceMember ref) {
805         out.format("    @%s(targetEntity=%s.class, fetch=%s.LAZY)\n", info.addType(ManyToOne.class),
806                 info.addType(ref.getFullClassName()), info.addType(FetchType.class));
807         writeJoinColumnsAnnotation(out, ref, true, !ref.isInPrimaryId(), !ref.isInPrimaryId());
808         writeField(out, ref);
809     }
810 
811     private void writeReferenceMembersZeroOneToMany(PrintStream out, ClassInfo info,
812             MyReferenceMember ref) {
813         out.format(
814                 "    @%s(mappedBy=\"%s\", cascade=%s.ALL, fetch=%s.LAZY, targetEntity=%s.class)\n",
815                 info.addType(OneToMany.class), ref.getMappedBy(), info.addType(CascadeType.class),
816                 info.addType(FetchType.class), info.addType(ref.getFullClassName()));
817         writeMultipleField(out, ref);
818     }
819 
820     private void writeReferenceMembersZeroOneToZeroOne(PrintStream out, ClassInfo info,
821             MyReferenceMember ref) {
822         if (info.getJavaClassSimpleName().compareTo(ref.getSimpleClassName()) < 0) {
823             // primary
824             out.format("    //primary side of relationship\n");
825             out.format("    @%s(mappedBy=\"%s\", fetch=%s.LAZY, targetEntity=%s.class)\n",
826                     info.addType(OneToOne.class), ref.getMappedBy(), info.addType(FetchType.class),
827                     info.addType(ref.getFullClassName()));
828         } else {
829             // secondary
830             out.format("    //secondary side of relationship\n");
831             out.format("    @%s(targetEntity=%s.class, fetch=%s.LAZY)\n",
832                     info.addType(OneToOne.class), info.addType(ref.getFullClassName()),
833                     info.addType(FetchType.class));
834             writeJoinColumnsAnnotation(out, ref, true, !ref.isInPrimaryId(), !ref.isInPrimaryId());
835         }
836         writeField(out, ref);
837     }
838 
839     private void writeReferenceMembersOneManyToOne(PrintStream out, ClassInfo info,
840             Set<String> validationMethods, MyReferenceMember ref) {
841         writeValidationNotNull(out, ref.getFieldName(), validationMethods);
842         out.format("    @%s(targetEntity=%s.class)\n", info.addType(ManyToOne.class),
843                 info.addType(ref.getFullClassName()));
844         writeJoinColumnsAnnotation(out, ref, false, !ref.isInPrimaryId(), !ref.isInPrimaryId());
845         writeField(out, ref);
846     }
847 
848     private void writeReferenceMembersManyToOne(PrintStream out, ClassInfo info,
849             MyReferenceMember ref) {
850         out.format("    @%s(targetEntity=%s.class, fetch=%s.LAZY)\n", info.addType(ManyToOne.class),
851                 ref.getFullClassName(), info.addType(FetchType.class));
852         writeJoinColumnsAnnotation(out, ref, false, !ref.isInPrimaryId(), !ref.isInPrimaryId());
853         writeField(out, ref);
854     }
855 
856     private void writeReferenceMembersOneToMany(PrintStream out, ClassInfo info,
857             MyReferenceMember ref) {
858         // ONE_TO_MANY and ONE_TO_ONE_MANY have PERSIST excluded from
859         // CascadeType so can do ONE_MANY to ONE_MANY via association
860         // class without persistence exceptions due to circular
861         // dependencies.
862         out.format(
863                 "    @%s(mappedBy=\"%s\", cascade={%3$s.MERGE,%3$s.REFRESH,%3$s.REMOVE}, fetch=%4$s.LAZY, targetEntity=%5$s.class)\n",
864                 info.addType(OneToMany.class), ref.getMappedBy(), info.addType(CascadeType.class),
865                 info.addType(FetchType.class), info.addType(ref.getFullClassName()));
866         writeMultipleField(out, ref);
867     }
868 
869     private void writeReferenceMembersZeroOneToOne(PrintStream out, ClassInfo info,
870             MyReferenceMember ref) {
871         out.format("    @%s(targetEntity=%s.class, cascade=%s.ALL, fetch=%s.LAZY)\n",
872                 info.addType(OneToOne.class), info.addType(ref.getFullClassName()),
873                 info.addType(CascadeType.class), info.addType(FetchType.class));
874         writeJoinColumnsAnnotation(out, ref, false, !ref.isInPrimaryId(), !ref.isInPrimaryId());
875         writeField(out, ref);
876     }
877 
878     private void writeReferenceMembersOneToZeroOne(PrintStream out, ClassInfo info,
879             MyReferenceMember ref) {
880         if (isUnary(ref, info)) {
881             out.format("    @%s(targetEntity=%s.class, cascade=%s.ALL, fetch=%s.LAZY)\n",
882                     info.addType(OneToOne.class), info.addType(ref.getFullClassName()),
883                     info.addType(CascadeType.class), info.addType(FetchType.class));
884 
885             writeJoinColumnsAnnotation(out, ref, true, !ref.isInPrimaryId(), !ref.isInPrimaryId());
886             writeField(out, ref);
887         } else {
888             out.format("    @%s(mappedBy=\"%s\", fetch=%s.LAZY, targetEntity=%s.class)\n",
889                     info.addType(OneToOne.class), ref.getMappedBy(), info.addType(FetchType.class),
890                     info.addType(ref.getFullClassName()));
891             writeField(out, ref);
892         }
893     }
894 
895     private void writeReferenceMembersOneToOne(PrintStream out, ClassInfo info,
896             Set<String> validationMethods, MyReferenceMember ref) {
897         // make an arbitrary deterministic decision about which side is
898         // annotated in which way
899         if (info.getJavaClassSimpleName().compareTo(ref.getSimpleClassName()) < 0) {
900             writeValidationNotNull(out, ref.getFieldName(), validationMethods);
901             out.format("    @%s(mappedBy=\"%s\", fetch=%s.LAZY, targetEntity=%s.class)\n",
902                     info.addType(OneToOne.class), ref.getMappedBy(), info.addType(FetchType.class),
903                     info.addType(ref.getFullClassName()));
904             writeField(out, ref);
905         } else {
906             writeReferenceMembersZeroOneToOne(out, info, ref);
907         }
908     }
909 
910     private boolean isUnary(MyReferenceMember ref, ClassInfo info) {
911         return ref.getFullClassName().equals(info.getClassFullName());
912     }
913 
914     private void writeValidationNotEmpty(PrintStream out, String fieldName,
915             Set<String> validationMethods) {
916         validationMethods.add("_validate" + Util.upperFirst(fieldName));
917         out.format("    private void _validate%s() {\n", Util.upperFirst(fieldName));
918         out.format("        if (%s.isEmpty())\n", fieldName);
919         out.format("            throw new %s(\"%s not established and is mandatory\");\n",
920                 info.addType(RelationshipNotEstablishedException.class), "?");
921         out.format("    }\n\n");
922 
923     }
924 
925     private void writeValidationNotNull(PrintStream out, String fieldName,
926             Set<String> validationMethods) {
927         validationMethods.add("_validate" + Util.upperFirst(fieldName));
928         out.format("    private void _validate%s() {\n", Util.upperFirst(fieldName));
929         out.format("        if (%s == null)\n", fieldName);
930         out.format("            throw new %s(\"%s not established and is mandatory\");\n",
931                 info.addType(RelationshipNotEstablishedException.class), "?");
932         out.format("    }\n\n");
933     }
934 
935     private void writeJoinColumnsAnnotation(PrintStream out, MyReferenceMember ref,
936             boolean nullable, boolean insertable, boolean updatable) {
937         HashSet<String> cols = new HashSet<String>();
938         for (MyJoinColumn col : ref.getJoinColumns()) {
939             cols.add(col.getThisColumnName());
940         }
941 
942         boolean twice = false;
943         info.getSpecializations();
944         for (MyReferenceMember r : info.getReferenceMembers()) {
945             if (r.getJoinColumns() != null && !ref.getFieldName().equals(r.getFieldName())) {
946                 for (MyJoinColumn col : r.getJoinColumns()) {
947                     if (cols.contains(col.getThisColumnName())) {
948                         twice = true;
949                     }
950                 }
951             }
952         }
953         out.format("    @%s(value={\n", info.addType(JoinColumns.class));
954         boolean first = true;
955         for (MyJoinColumn col : ref.getJoinColumns()) {
956             if (!first)
957                 out.format(",\n");
958             first = false;
959             out.format(
960                     "        @%s(name=\"%s\", referencedColumnName=\"%s\", nullable=%s, insertable=%s, updatable=%s)",
961                     info.addType(JoinColumn.class), col.getThisColumnName(),
962                     col.getOtherColumnName(), nullable, insertable && !twice, updatable && !twice);
963         }
964         out.format("})\n");
965     }
966 
967     private void writeIdGetterAndSetter(PrintStream out, ClassInfo info) {
968         jd(out, "Returns the identifier for this entity.", "    ");
969         out.format("    public %s getId() {\n", info.addType(getIdType(info)));
970         out.format("        return id;\n");
971         out.format("    }\n\n");
972         out.format("    public void setId(%s id) {\n", info.addType(getIdType(info)));
973         out.format("        this.id = id;\n");
974         out.format("    }\n\n");
975         out.format("    public %s setId_(%s id) {\n", info.getJavaClassSimpleName(),
976                 info.addType(getIdType(info)));
977         out.format("        this.id = id;\n");
978         out.format("        return this;\n");
979         out.format("    }\n\n");
980     }
981 
982     private void writeNonIdIndependentAttributeGettersAndSetters(PrintStream out, ClassInfo info) {
983         for (MyIndependentAttribute attribute : info.getNonPrimaryIdIndependentAttributeMembers()) {
984             writeIndependentAttributeGetterAndSetter(out, attribute);
985         }
986     }
987 
988     private void writeStateGetterAndSetter(PrintStream out, ClassInfo info) {
989         if (info.hasBehaviour()) {
990             jd(out, STATE_COMMENT, "    ");
991             out.format("    public String getState(){\n");
992             out.format("        return state;\n");
993             out.format("    }\n\n");
994             jd(out, STATE_COMMENT, "    ");
995             out.format("    public void setState(String state){\n");
996             out.format("        this.state = state;\n");
997             out.format("    }\n\n");
998             jd(out, "Sets the current state. This should only be used when creating an instance without using the state machine.",
999                     "    ");
1000             out.format("    public void setState(%s state){\n",
1001                     info.addType(info.getClassFullName() + ".State"));
1002             out.format("        this.state = state.toString();\n");
1003             out.format("    }\n\n");
1004         }
1005     }
1006 
1007     private void writeStates(PrintStream out, ClassInfo info) {
1008         if (info.hasBehaviour()) {
1009             jd(out, "The list of all states from the state machine for this entity.", "    ");
1010             out.format("    public static enum State {\n");
1011             boolean first = true;
1012             out.format("        ");
1013             for (String state : info.getStateNames()) {
1014                 if (!first)
1015                     out.format(",");
1016                 out.format(info.getStateAsJavaIdentifier(state));
1017                 first = false;
1018             }
1019             out.format(";\n");
1020             out.format("    }\n\n");
1021         }
1022     }
1023 
1024     private void writeEvents(PrintStream out, ClassInfo info) {
1025         List<MyEvent> events = info.getEvents();
1026         if (events.size() == 0)
1027             return;
1028 
1029         // create Events static class and each Event declared within
1030         jd(out, "Event declarations.", "    ");
1031         out.format("    public static class Events {\n");
1032 
1033         // write state names that have signatures
1034         Map<String, MyEvent> stateEvent = Maps.newHashMap();
1035         for (MyEvent event : info.getEvents()) {
1036             if (event.getStateName() != null)
1037                 stateEvent.put(event.getStateName(), event);
1038         }
1039 
1040         for (MyEvent event : stateEvent.values()) {
1041             jd(out, "Event signature for the state '" + event.getStateName() + "'", "        ");
1042             out.format("        public static interface %s {\n\n",
1043                     event.getStateSignatureInterfaceSimpleName());
1044             // getters
1045             for (MyParameter p : event.getParameters()) {
1046                 out.format("            %s get%s();\n\n", info.addType(p.getType()),
1047                         Util.upperFirst(p.getFieldName()));
1048 
1049             }
1050             out.format("        }\n\n");
1051         }
1052 
1053         for (MyEvent event : info.getEvents()) {
1054             String stateSignatureImplements;
1055             if (event.getStateName() != null)
1056                 stateSignatureImplements = ", " + event.getStateSignatureInterfaceSimpleName();
1057             else
1058                 stateSignatureImplements = "";
1059             String creationEventImplements;
1060             if (event.getCreates()) {
1061                 creationEventImplements = ", " + info.addType(CreationEvent.class) + "<"
1062                         + info.getJavaClassSimpleName() + ">";
1063             } else
1064                 creationEventImplements = "";
1065             out.println();
1066             jd(out, "Event implementation for event '" + event.getName() + "'", "        ");
1067 
1068             out.format("        @%s(\"serial\")\n", info.addType(SuppressWarnings.class));
1069             out.format("        public static class %s implements %s<%s>, %s%s%s {\n\n",
1070                     event.getSimpleClassName(), info.addType(Event.class),
1071                     info.getJavaClassSimpleName(), info.addType(Serializable.class),
1072                     stateSignatureImplements, creationEventImplements);
1073 
1074             // add signature key method
1075             StringBuffer signature = new StringBuffer();
1076             for (MyParameter p : event.getParameters()) {
1077                 signature.append(p.getType());
1078                 signature.append(";");
1079             }
1080 
1081             out.format("            public static final String signatureKey = \"%s\";\n\n",
1082                     signature.toString());
1083 
1084             out.format("            public String signatureKey() {\n");
1085             out.format("                return signatureKey;\n");
1086             out.format("            }\n");
1087 
1088             StringBuilder constructorBody = new StringBuilder();
1089             for (MyParameter p : event.getParameters()) {
1090                 constructorBody.append(String.format(
1091                         "                if (%s == null) throw new %s(\"%s cannot be null\");\n",
1092                         p.getFieldName(), info.addType(NullPointerException.class),
1093                         p.getFieldName()));
1094                 constructorBody.append("                this." + p.getFieldName() + " = "
1095                         + p.getFieldName() + ";\n");
1096             }
1097 
1098             StringBuilder constructor = new StringBuilder();
1099             constructor.append("            public " + event.getSimpleClassName() + "(");
1100             boolean first = true;
1101             for (MyParameter p : event.getParameters()) {
1102                 out.format("            private final %s %s;\n", info.addType(p.getType()),
1103                         p.getFieldName());
1104                 if (!first)
1105                     constructor.append(", ");
1106                 constructor.append(info.addType(p.getType()) + " " + p.getFieldName());
1107                 first = false;
1108             }
1109             constructor.append("){\n");
1110             constructor.append(constructorBody);
1111             constructor.append("            }\n");
1112             out.println();
1113             jd(out, "Constructor.", "            ");
1114             out.println(constructor);
1115 
1116             // getters
1117             for (MyParameter p : event.getParameters()) {
1118                 out.format("            public %s get%s(){\n", info.addType(p.getType()),
1119                         Util.upperFirst(p.getFieldName()));
1120                 out.format("                return %s;\n", p.getFieldName());
1121                 out.format("            }\n\n");
1122             }
1123 
1124             // create constructor using Builder
1125             out.format("            private %s(Builder builder) {\n", event.getSimpleClassName());
1126             for (MyParameter p : event.getParameters()) {
1127                 out.format(
1128                         "                if (builder.%s == null) throw new %s(\"%s cannot be null\");\n",
1129                         p.getFieldName(), info.addType(NullPointerException.class),
1130                         p.getFieldName());
1131                 out.format("                this.%s = builder.%s;\n", p.getFieldName(),
1132                         p.getFieldName());
1133             }
1134             out.format("            }\n\n");
1135 
1136             out.format("            public static Builder builder() {\n");
1137             out.format("                return new Builder();\n");
1138             out.format("            }\n\n");
1139 
1140             // define event Builder class
1141             out.format("            public static class Builder {\n");
1142             out.println();
1143             for (MyParameter p : event.getParameters()) {
1144                 out.format("                private %s %s;\n", info.addType(p.getType()),
1145                         p.getFieldName());
1146             }
1147             for (MyParameter p : event.getParameters()) {
1148                 out.println();
1149                 out.format("                public Builder %s(%s %s) {\n", p.getFieldName(),
1150                         info.addType(p.getType()), p.getFieldName());
1151                 out.format("                    this.%s = %s;\n", p.getFieldName(),
1152                         p.getFieldName());
1153                 out.format("                    return this;\n");
1154                 out.format("                }\n");
1155             }
1156 
1157             out.println();
1158             out.format("                public %s build() {\n", event.getSimpleClassName());
1159             out.format("                    return new %s(this);\n", event.getSimpleClassName());
1160             out.format("                }\n");
1161 
1162             out.format("            }\n");
1163 
1164             if (event.getParameters().size() > 0) {
1165                 out.println();
1166                 out.format("            @%s\n", info.addType(Override.class));
1167                 out.format("            public String toString() {\n");
1168                 out.format("                return %s.toStringHelper(this.getClass())\n",
1169                         info.addType(MoreObjects.class));
1170                 for (MyParameter p : event.getParameters()) {
1171                     out.format("                    .add(\"%s\", %s)\n", p.getFieldName(),
1172                             p.getFieldName());
1173                 }
1174                 out.format("                    .toString();\n");
1175                 out.format("            }\n");
1176             }
1177 
1178             // close event class definition
1179             out.format("        }\n\n");
1180 
1181         }
1182         out.format("    }\n\n");
1183     }
1184 
1185     private void writePreUpdateCheck(PrintStream out, ClassInfo info,
1186             Set<String> validationMethods) {
1187         jd(out, "Calls all validation methods just before updating database.", "    ");
1188         out.format("    @%s\n", info.addType(PreUpdate.class));
1189         out.format("    void validateBeforeUpdate(){\n");
1190         for (String methodName : validationMethods)
1191             out.format("        %s();\n", methodName);
1192         out.format("    }\n\n");
1193 
1194         jd(out, "Calls all validation methods just before first persist of this entity.", "    ");
1195         out.format("    @%s\n", info.addType(PrePersist.class));
1196         out.format("    void validateBeforePersist(){\n");
1197         for (String methodName : validationMethods)
1198             out.format("        %s();\n", methodName);
1199         out.format("    }\n\n");
1200     }
1201 
1202     private boolean isRelationship(MyReferenceMember ref, Multhref="../../../../xuml/tools/model/compiler/info/Mult.html#Mult">Mult here, Mult there) {
1203         return ref.getThisMult().equals(here) && ref.getThatMult().equals(there);
1204     }
1205 
1206     private void writeManyToMany(PrintStream out, ClassInfo info, MyReferenceMember ref) {
1207         if (info.getJavaClassSimpleName().compareTo(ref.getSimpleClassName()) < 0) {
1208             // primary
1209             writeManyToManyPrimarySide(out, info, ref);
1210         } else {
1211             // secondary
1212             writeManyToManySecondarySide(out, info, ref);
1213         }
1214     }
1215 
1216     private void writeManyToManyPrimarySide(PrintStream out, ClassInfo info,
1217             MyReferenceMember ref) {
1218         out.format("    //primary side of relationship\n");
1219         out.format("    @%s(targetEntity=%s.class, cascade=%s.ALL, fetch=%s.LAZY)\n",
1220                 info.addType(ManyToMany.class), info.addType(ref.getFullClassName()),
1221                 info.addType(CascadeType.class), info.addType(FetchType.class));
1222 
1223         writeJoinTableAnnotation(out, info, ref.getJoinTable());
1224 
1225         writeMultipleField(out, ref);
1226     }
1227 
1228     private void writeJoinTableAnnotation(PrintStream out, ClassInfo info, MyJoinTable jt) {
1229         out.format("    @%s(name=\"%s\", schema=\"%s\",\n", info.addType(JoinTable.class),
1230                 jt.getJoinTable(), jt.getJoinTableSchema());
1231 
1232         out.format("            joinColumns={\n");
1233         writeJoinColumns(out, info, jt.getJoinColumns());
1234         out.format("},\n");
1235         out.format("            inverseJoinColumns={\n");
1236         writeJoinColumns(out, info, jt.getInverseJoinColumns());
1237         out.format("})\n");
1238     }
1239 
1240     private void writeJoinColumns(PrintStream out, ClassInfo info, List<MyJoinColumn> cols) {
1241         boolean first = true;
1242         for (MyJoinColumn jc : cols) {
1243             if (!first)
1244                 out.format(",\n");
1245             out.format("                @%s(name=\"%s\", referencedColumnName=\"%s\")",
1246                     info.addType(JoinColumn.class), jc.getThisColumnName(),
1247                     jc.getOtherColumnName());
1248             first = false;
1249         }
1250     }
1251 
1252     private void writeManyToManySecondarySide(PrintStream out, ClassInfo info,
1253             MyReferenceMember ref) {
1254         out.format("    //secondary side of relationship\n");
1255         out.format(
1256                 "    @%s(mappedBy=\"%s\", targetEntity=%s.class, cascade=%s.ALL, fetch=%s.LAZY)\n",
1257                 info.addType(ManyToMany.class), ref.getMappedBy(),
1258                 info.addType(ref.getFullClassName()), info.addType(CascadeType.class),
1259                 info.addType(FetchType.class));
1260         writeMultipleField(out, ref);
1261     }
1262 
1263     private void writeField(PrintStream out, MyReferenceMember ref) {
1264         out.format("    private %s %s;\n\n", info.addType(ref.getFullClassName()),
1265                 ref.getFieldName());
1266         writeGetterAndSetter(out, info, ref.getSimpleClassName(), ref.getFullClassName(),
1267                 ref.getFieldName(), false, getReferenceJavadoc(info, ref));
1268         writeRelateTo(out, ref);
1269         writeUnrelateTo(out, ref);
1270     }
1271 
1272     private boolean isUnary(MyReferenceMember ref) {
1273         return ref.getFullClassName().equals(info.getClassFullName());
1274     }
1275 
1276     private void writeRelateTo(PrintStream out, MyReferenceMember ref) {
1277         // TODO handle unary relationship relateTo
1278         // TODO handle association classes (relateAcrossR1Using)
1279         if (isUnary(ref))
1280             return;
1281 
1282         String fieldName = ref.getFieldName();
1283         String mappedBy = Util.lowerFirst(ref.getMappedBy());
1284         writeReferenceJavadoc(out, info, ref);
1285         out.format("    public %s relateAcrossR%s(%s %s) {\n", info.getJavaClassSimpleName(),
1286                 ref.getRnum(), info.addType(ref.getFullClassName()), fieldName);
1287         Mult thisMult = ref.getThisMult();
1288         Mult thatMult = ref.getThatMult();
1289 
1290         // set the local field
1291         if (thatMult.equals(Mult.ONE) || thatMult.equals(Mult.ZERO_ONE)) {
1292             out.format("        set%s(%s);\n", Util.upperFirst(fieldName), fieldName);
1293         } else {
1294             // this commented out because can hurt performance terribly
1295             // hydrating a huge collection just so can add to it
1296             // out.format(" get%s().add(%s);\n", Util.upperFirst(fieldName),
1297             // fieldName);
1298         }
1299         // set the field on the other object
1300         if (thisMult.equals(Mult.ONE) || thisMult.equals(Mult.ZERO_ONE)) {
1301             out.format("        %s.set%s(this);\n", fieldName, Util.upperFirst(mappedBy));
1302         } else {
1303             // this commented out because can hurt performance terribly
1304             // hydrating a huge collection just so can add to it
1305             // out.format(" %s.get%s().add(this);\n", fieldName,
1306             // Util.upperFirst(mappedBy),
1307             // fieldName);
1308         }
1309         out.format("        return this;\n");
1310         out.format("    }\n\n");
1311     }
1312 
1313     private void writeUnrelateTo(PrintStream out, MyReferenceMember ref) {
1314         // TODO handle unary relationship relateTo
1315         // TODO handle association classes (relateAcrossR1Using)
1316         if (isUnary(ref))
1317             return;
1318 
1319         String fieldName = ref.getFieldName();
1320         String mappedBy = Util.lowerFirst(ref.getMappedBy());
1321         writeReferenceJavadoc(out, info, ref);
1322         out.format("    public %s unrelateAcrossR%s(%s %s) {\n", info.getJavaClassSimpleName(),
1323                 ref.getRnum(), info.addType(ref.getFullClassName()), fieldName);
1324         Mult thisMult = ref.getThisMult();
1325         Mult thatMult = ref.getThatMult();
1326 
1327         // set the local field
1328         if (thatMult.equals(Mult.ONE) || thatMult.equals(Mult.ZERO_ONE)) {
1329             out.format("        set%s(null);\n", Util.upperFirst(fieldName), fieldName);
1330         } else {
1331             out.format("        get%s().remove(%s);\n", Util.upperFirst(fieldName), fieldName);
1332         }
1333         // set the field on the other object
1334         if (thisMult.equals(Mult.ONE) || thisMult.equals(Mult.ZERO_ONE)) {
1335             out.format("        %s.set%s(null);\n", fieldName, Util.upperFirst(mappedBy));
1336         } else {
1337             out.format("        %s.get%s().remove(this);\n", fieldName, Util.upperFirst(mappedBy),
1338                     fieldName);
1339         }
1340 
1341         out.format("        return this;\n");
1342         out.format("    }\n\n");
1343     }
1344 
1345     private void writeGetterAndSetter(PrintStream out, ClassInfo info, String simpleClassName,
1346             String fullClassName, String fieldName, boolean isMultiple, String javadoc) {
1347         String type;
1348         if (isMultiple)
1349             type = info.addType(new Type(Set.Typeeyword">class.getName(), new Type(fullClassName)));
1350         else
1351             type = info.addType(fullClassName);
1352         // write getter and setter
1353         jd(out, "Getter. " + javadoc, "    ");
1354         out.format("    public %s get%s(){\n", type, Util.upperFirst(fieldName));
1355         out.format("        return %s;\n", fieldName);
1356         out.format("    }\n\n");
1357         jd(out, "Setter. " + javadoc, "    ");
1358         out.format("    public void set%s(%s %s){\n", Util.upperFirst(fieldName), type, fieldName);
1359         out.format("        this.%1$s=%1$s;\n", fieldName);
1360         out.format("    }\n\n");
1361     }
1362 
1363     private void writeMultipleField(PrintStream out, MyReferenceMember ref) {
1364         out.format("    private %s %s = %s.newHashSet();\n\n",
1365                 info.addType(new Type(Set.Typeeyword">class.getName(), new Type(ref.getFullClassName()))),
1366                 ref.getFieldName(), info.addType(Sets.class));
1367         writeGetterAndSetter(out, info, ref.getSimpleClassName(), ref.getFullClassName(),
1368                 ref.getFieldName(), true, getReferenceJavadoc(info, ref));
1369         writeRelateTo(out, ref);
1370         writeUnrelateTo(out, ref);
1371     }
1372 
1373     private void writeSignalMethods(PrintStream out, ClassInfo info) {
1374 
1375         // add event call methods
1376         jd(out, "Asychronously queues the given signal against this entity for processing.",
1377                 "    ");
1378         out.format("    @%s\n", info.addType(Override.class));
1379         out.format("    public %s signal(%s<%s> event) {\n", info.getJavaClassSimpleName(),
1380                 info.addType(Event.class), info.getJavaClassSimpleName());
1381         if (info.hasBehaviour())
1382             out.format("        helper().signal(event);\n");
1383         else
1384             out.format("        //no behaviour for this class\n");
1385         out.format("        return this;\n");
1386         out.format("    }\n\n");
1387 
1388         jd(out, "Asychronously queues the given signal against this entity for processing\nafter the delay specified. Delay cannot be null.",
1389                 "    ");
1390         out.format("    @%s\n", info.addType(Override.class));
1391         out.format("    public %s signal(%s<%s> event, %s delay) {\n",
1392                 info.getJavaClassSimpleName(), info.addType(Event.class),
1393                 info.getJavaClassSimpleName(), info.addType(Duration.class));
1394         if (info.hasBehaviour())
1395             // TODO not right, should be sending object uniqueId as from, get
1396             // from ThreadLocal
1397             out.format("        helper().signal(event, %s.of(delay));\n",
1398                     info.addType(Optional.class));
1399         else
1400             out.format("        //no behaviour for this class\n");
1401         out.format("        return this;\n");
1402         out.format("    }\n\n");
1403 
1404         jd(out, "Asychronously queues the given signal against this entity for processing\nat the epoch time in ms specified. Delay cannot be null.",
1405                 "    ");
1406         out.format("    @%s\n", info.addType(Override.class));
1407         out.format("    public %s signal(%s<%s> event, long time) {\n",
1408                 info.getJavaClassSimpleName(), info.addType(Event.class),
1409                 info.getJavaClassSimpleName());
1410         out.format(
1411                 "        return signal(event, %s.create(time-%s.currentTimeMillis(),%s.MILLISECONDS));\n",
1412                 info.addType(Duration.class), info.addType(System.class),
1413                 info.addType(TimeUnit.class));
1414         out.format("    }\n\n");
1415 
1416         out.format("    public %s cancelSignal(String eventSignatureKey) {\n ",
1417                 info.getJavaClassSimpleName());
1418         // TODO implement cancelSignal
1419         out.format("        return this;\n");
1420         out.format("    }\n\n");
1421 
1422         out.format("    public %s cancelSignal(Event<%s> event) {\n ",
1423                 info.getJavaClassSimpleName(), info.getJavaClassSimpleName());
1424         // TODO implement cancelSignal
1425         out.format("        return cancelSignal(event.signatureKey());\n");
1426         out.format("    }\n\n");
1427 
1428         jd(out, "Synchronously runs the on entry procedure associated\n"
1429                 + "with this event and also any signals to self that are\n"
1430                 + "made during the procedure. This method should not\n"
1431                 + "be called directly except in a unit testing scenario\n"
1432                 + "perhaps. Call signal method instead.", "    ");
1433         out.format("    @%s\n", info.addType(Override.class));
1434         out.format("    public %s event(%s<%s> event){\n\n", info.getJavaClassSimpleName(),
1435                 info.addType(Event.class), info.getJavaClassSimpleName());
1436         if (info.hasBehaviour()) {
1437             out.format("        helper().beforeEvent();\n\n");
1438             out.format("        // process the event\n");
1439             boolean first = true;
1440             for (MyEvent event : info.getEvents()) {
1441                 out.format("        ");
1442                 if (!first)
1443                     out.format("else ");
1444                 out.format("if (event instanceof Events.%s){\n", event.getSimpleClassName());
1445                 out.format("            processEvent((Events.%s) event);\n",
1446                         event.getSimpleClassName());
1447                 out.format("        }\n");
1448                 first = false;
1449             }
1450             out.println();
1451             out.format("        helper().afterEvent();\n");
1452         }
1453         out.format("        return this;\n");
1454         out.format("    }\n\n");
1455         if (info.hasBehaviour()) {
1456             for (MyEvent event : info.getEvents()) {
1457 
1458                 jd(out, "Synchronously perform the change.", "    ");
1459 
1460                 out.format("    private void processEvent(Events.%s event){\n",
1461                         event.getSimpleClassName());
1462                 boolean first = true;
1463                 for (MyTransition transition : info.getTransitions()) {
1464                     // constraint is no event overloading
1465                     if (transition.getEventName().equals(event.getName())) {
1466                         if (first)
1467                             out.format("        if");
1468                         else
1469                             out.format("        else if");
1470                         first = false;
1471                         if (transition.getFromState() == null)
1472                             // handle creation state
1473                             out.format(" (state==null){\n");
1474                         else
1475                             out.format(" (state.equals(State.%s.toString())){\n",
1476                                     info.getStateAsJavaIdentifier(transition.getFromState()));
1477                         out.format("            state=State.%s.toString();\n",
1478                                 info.getStateAsJavaIdentifier(transition.getToState()));
1479                         out.format("            synchronized(this) {\n");
1480                         out.format("                _behaviour.onEntry%s(event);\n",
1481                                 Util.upperFirst(Util.toJavaIdentifier(transition.getToState())));
1482                         out.format("            }\n");
1483                         out.format("        }\n");
1484                     }
1485                 }
1486                 out.format("    }\n\n");
1487             }
1488         }
1489     }
1490 
1491     private void writeStaticCreateMethods(PrintStream out, ClassInfo info) {
1492         if (info.hasBehaviour()) {
1493             for (MyTransition t : info.getTransitions()) {
1494                 if (t.isCreationTransition()) {
1495                     jd(out, "Static creator method associated with the creation transition to '"
1496                             + t.getToState() + "' via event '" + t.getEventName() + "'.", "    ");
1497                     out.format("    public static %s create(%s em, %s<%s> event) {\n",
1498                             info.getJavaClassSimpleName(), info.addType(EntityManager.class),
1499                             info.addType(CreationEvent.class), info.getJavaClassSimpleName());
1500                     out.format("        %s entity = new %s();\n", info.getJavaClassSimpleName(),
1501                             info.getJavaClassSimpleName());
1502                     out.format("        entity.event(event);\n");
1503                     out.format("        em.persist(entity);\n");
1504                     out.format("        return entity;\n");
1505                     out.format("    }\n\n");
1506                 }
1507             }
1508         }
1509     }
1510 
1511     private void writeMergeMethod(PrintStream out, ClassInfo info) {
1512         jd(out, "Same as EntityManager.merge() except allows method chaining.\n"
1513                 + "Returns a new merged instance.", "    ");
1514         out.format("    public %s merge(%s em) {\n", info.getJavaClassSimpleName(),
1515                 info.addType(EntityManager.class));
1516         out.format("        return em.merge(this);\n");
1517         out.format("    }\n\n");
1518     }
1519 
1520     private void writePersistMethod(PrintStream out, ClassInfo info) {
1521         jd(out, "Same as EntityManager.persist() except allows method chaining. Returns this.",
1522                 "    ");
1523         out.format("    public %s persist(%s em) {\n", info.getJavaClassSimpleName(),
1524                 info.addType(EntityManager.class));
1525         out.format("        em.persist(this);\n");
1526         out.format("        return this;\n");
1527         out.format("    }\n\n");
1528         out.println();
1529         jd(out, "Same as {@code persist(Context.em())}. Returns this.", "    ");
1530         out.format("    public %s persist() {\n", info.getJavaClassSimpleName(),
1531                 info.addType(EntityManager.class));
1532         out.format("        Context.em().persist(this);\n");
1533         out.format("        return this;\n");
1534         out.format("    }\n\n");
1535     }
1536 
1537     private void writeRefreshMethod(PrintStream out, ClassInfo info) {
1538         jd(out, "Same as EntityManager.refresh() except inverted to facilitate method chaining. Returns this.",
1539                 "    ");
1540         out.format("    public %s refresh(%s em) {\n", info.getJavaClassSimpleName(),
1541                 info.addType(EntityManager.class));
1542         out.format("        em.refresh(this);\n");
1543         out.format("        return this;\n");
1544         out.format("    }\n\n");
1545     }
1546 
1547     private void writeRemoveMethod(PrintStream out, ClassInfo info) {
1548         jd(out, "Same as EntityManager.remove() except inverted to facilitate method chaining. Returns this.",
1549                 "    ");
1550         out.format("    public %s remove(%s em) {\n", info.getJavaClassSimpleName(),
1551                 info.addType(EntityManager.class));
1552         out.format("        em.remove(this);\n");
1553         out.format("        return this;\n");
1554         out.format("    }\n\n");
1555 
1556         jd(out, "Same as EntityManager.remove() except inverted to facilitate method chaining. Returns this.",
1557                 "    ");
1558         out.format("    public %s remove() {\n", info.getJavaClassSimpleName(),
1559                 info.addType(EntityManager.class));
1560         out.format("        Context.remove(this);\n");
1561         out.format("        return this;\n");
1562         out.format("    }\n\n");
1563 
1564         jd(out, "Same as this.remove()", "    ");
1565         out.format("    public %s delete() {\n", info.getJavaClassSimpleName(),
1566                 info.addType(EntityManager.class));
1567         out.format("        return remove();\n");
1568         out.format("    }\n\n");
1569     }
1570 
1571     private void writeLoadMethod(PrintStream out, ClassInfo info) {
1572         jd(out, "Does a merge then a refresh and returns a new updated merged instance.", "    ");
1573         out.format("    public %s load(%s em) {\n", info.getJavaClassSimpleName(),
1574                 info.addType(EntityManager.class));
1575         out.format("        return merge(em).refresh(em);\n");
1576         out.format("    }\n\n");
1577 
1578         out.format("    public %s load() {\n", info.getJavaClassSimpleName());
1579         out.format("        return Context.load(this);\n");
1580         out.format("    }\n\n");
1581 
1582     }
1583 
1584     private void writeToStringMethod(PrintStream out, ClassInfo info) {
1585         // TODO
1586     }
1587 
1588     private void writeBehaviourInterface(PrintStream out, ClassInfo info) {
1589 
1590         if (info.getEvents().size() == 0)
1591             return;
1592         jd(out, "On entry procedures for this entity.", "    ");
1593         out.format("    public static interface Behaviour {\n\n");
1594 
1595         // write state names that have signatures
1596         Map<String, MyEvent> stateEvent = Maps.newLinkedHashMap();
1597         for (MyEvent event : info.getEvents()) {
1598             if (event.getStateName() != null)
1599                 stateEvent.put(event.getStateName(), event);
1600         }
1601         List<MyEvent> nonStateEvents = Lists.newArrayList();
1602         for (MyEvent event : info.getEvents()) {
1603             if (event.getStateName() == null)
1604                 nonStateEvents.add(event);
1605         }
1606 
1607         // ensure method names don't get written twice when there are two
1608         // transitions to the same state from different states using the same
1609         // signal
1610         Set<String> methods = new HashSet<String>();
1611 
1612         for (MyEvent event : stateEvent.values()) {
1613             String methodSuffix = Util.upperFirst(Util.toJavaIdentifier(event.getStateName()));
1614             methods.add(String.format("        void onEntry%s(Events.%s event);\n\n", methodSuffix,
1615                     event.getStateSignatureInterfaceSimpleName()));
1616         }
1617 
1618         for (MyEvent event : nonStateEvents) {
1619             for (MyTransition transition : info.getTransitions()) {
1620                 // constraint is no event overloading
1621                 String methodSuffix = Util
1622                         .upperFirst(Util.toJavaIdentifier(transition.getToState()));
1623                 if (transition.getEventName().equals(event.getName())) {
1624                     methods.add(String.format("        void onEntry%s(Events.%s event);\n\n",
1625                             methodSuffix, event.getSimpleClassName()));
1626                 }
1627             }
1628         }
1629 
1630         for (String method : methods)
1631             out.format(method);
1632 
1633         out.format("    }\n\n");
1634     }
1635 
1636     private void writeBehaviourFactoryInterface(PrintStream out, ClassInfo info) {
1637         if (info.getEvents().size() == 0)
1638             return;
1639         jd(out, "A factory that creates behaviour for a given entity.", "    ");
1640         out.format("    public static interface BehaviourFactory {\n\n");
1641         out.format("        Behaviour create(%s entity);\n\n", info.getJavaClassSimpleName());
1642         out.format("    }\n\n");
1643     }
1644 
1645     private void writeBehaviourFactoryCreator(PrintStream out, ClassInfo info) {
1646         if (info.getEvents().size() == 0)
1647             return;
1648         jd(out, "Returns a BehaviourFactory on the assumption that the given class\nhas a single constructor with one parameter of type "
1649                 + info.getJavaClassSimpleName() + ".", "    ");
1650         out.format(
1651                 "    public static BehaviourFactory createBehaviourFactory(final Class<? extends Behaviour> cls) {\n");
1652         out.format("        return new BehaviourFactory() {\n");
1653         out.format("            @%s\n", info.addType(Override.class));
1654         out.format("            public Behaviour create(%s entity) {\n",
1655                 info.getJavaClassSimpleName());
1656         out.format("                if (cls.getConstructors().length != 1)\n");
1657         out.format("                     throw new RuntimeException(\n");
1658         out.format(
1659                 "                              \"expected only one constructor in the Behaviour implementation\");\n");
1660         out.format("                try {\n");
1661         // TODO add error message about not having a single parameter
1662         // constructor with certain type
1663         out.format(
1664                 "                    return (Behaviour) cls.getConstructors()[0].newInstance(entity);\n");
1665         out.format("                } catch (%s e) {\n",
1666                 info.addType(InstantiationException.class));
1667         out.format("                    throw new RuntimeException(e);\n");
1668         out.format("                } catch (%s e) {\n",
1669                 info.addType(IllegalAccessException.class));
1670         out.format("                    throw new RuntimeException(e);\n");
1671         out.format("                } catch (%s e) {\n",
1672                 info.addType(IllegalArgumentException.class));
1673         out.format("                    throw new RuntimeException(e);\n");
1674         out.format("                } catch (%s e) {\n",
1675                 info.addType(InvocationTargetException.class));
1676         out.format("                    throw new RuntimeException(e);\n");
1677         out.format("                } catch (%s e) {\n", info.addType(SecurityException.class));
1678         out.format("                    throw new RuntimeException(e);\n");
1679         out.format("                }\n");
1680         out.format("            }\n");
1681         out.format("        };\n");
1682         out.format("    }\n\n");
1683     }
1684 
1685     private void writeIndependentAttributeMember(PrintStream out, MyIndependentAttribute attribute,
1686             String indent) {
1687         writeIndependentAttributeMember(out, attribute.getFieldName(), attribute.getColumnName(),
1688                 attribute.isNullable(), indent, attribute.getType(), attribute.getExtensions());
1689     }
1690 
1691     private void writeIndependentAttributeMember(PrintStream out, String fieldName,
1692             String columnName, boolean isNullable, String indent, MyTypeDefinition type,
1693             MyAttributeExtensions extensions) {
1694         if (extensions.getDocumentationContent() != null)
1695             jd(out, extensions.getDocumentationContent(), indent);
1696         writeGeneratedAnnotation(out, extensions.isGenerated(), indent);
1697         writeFieldAnnotation(out, columnName, isNullable, indent, type, true, true);
1698         writeField(out, info, type, fieldName, indent);
1699     }
1700 
1701     private void writeGeneratedAnnotation(PrintStream out, boolean generated, String indent) {
1702         if (generated) {
1703             out.format("%s@%s(strategy=%s.AUTO)\n", indent, info.addType(GeneratedValue.class),
1704                     info.addType(GenerationType.class));
1705         }
1706     }
1707 
1708     private void writeFieldAnnotation(PrintStream out, String columnName, boolean isNullable,
1709             String indent, MyTypeDefinition type, boolean insertable, boolean updatable) {
1710         final String length;
1711         boolean isLong = type.getMyType().equals(MyType.STRING)
1712                 && type.getMaxLength().compareTo(BigInteger.valueOf(MAX_VARCHAR_LENGTH)) > 0;
1713         if ((type.getMyType().equals(MyType.STRING) && isLong)
1714                 || (type.getMyType().equals(MyType.BYTES))) {
1715             out.format("%s@%s\n", indent, info.addType(Lob.class));
1716             out.format("%s@%s(fetch=%s.LAZY)\n", indent, info.addType(Basic.class),
1717                     info.addType(FetchType.class));
1718 
1719         }
1720         if (type.getMyType().equals(MyType.STRING) && !isLong)
1721             length = ",length=" + type.getMaxLength();
1722         else
1723             length = "";
1724         final String insertableParameter;
1725         if (!insertable)
1726             insertableParameter = ",insertable=false";
1727         else
1728             insertableParameter = "";
1729         final String updatableParameter;
1730         if (!updatable)
1731             updatableParameter = ",updatable=false";
1732         else
1733             updatableParameter = "";
1734         final String precision;
1735         if (type.getMyType().equals(MyType.REAL))
1736             precision = ",precision=" + type.getPrecision();
1737         else
1738             precision = "";
1739 
1740         out.format("%s@%s(name=\"%s\", nullable=%s%s%s%s%s)\n", indent, info.addType(Column.class),
1741                 columnName, isNullable, length, insertableParameter, updatableParameter, precision);
1742         if (type.getMyType().equals(MyType.DATE))
1743             out.format("%s@%s(%s.DATE)\n", indent, info.addType(Temporal.class),
1744                     info.addType(TemporalType.class));
1745         else if (type.getMyType().equals(MyType.TIMESTAMP))
1746             out.format("%s@%s(%s.TIMESTAMP)\n", indent, info.addType(Temporal.class),
1747                     info.addType(TemporalType.class));
1748     }
1749 
1750     private void writeIndependentAttributeGetterAndSetter(PrintStream out,
1751             MyIndependentAttribute attribute) {
1752         String type = info.addType(attribute.getType().getType());
1753         String doco = getDocumentation(attribute);
1754         jd(out, "Returns " + attribute.getFieldName() + ". " + doco, "    ");
1755         if (attribute.getFieldName().equals("id")) {
1756             info.addType(Override.class);
1757             out.format("    @Override\n");
1758         }
1759         out.format("    public %s get%s(){\n", type, Util.upperFirst(attribute.getFieldName()));
1760         out.format("        return %s;\n", attribute.getFieldName());
1761         out.format("    }\n\n");
1762 
1763         jd(out, "Sets " + attribute.getFieldName() + " to the given value. " + doco, "    ");
1764         out.format("    public void set%s(%s %s){\n", Util.upperFirst(attribute.getFieldName()),
1765                 type, attribute.getFieldName());
1766         out.format("        this.%1$s = %1$s;\n", attribute.getFieldName());
1767         out.format("    }\n\n");
1768 
1769         jd(out, "Sets the attribute to the given value and returns this\n(enables method chaining).",
1770                 "    ");
1771         out.format("    public %s set%s_(%s %s){\n", info.getJavaClassSimpleName(),
1772                 Util.upperFirst(attribute.getFieldName()), type, attribute.getFieldName());
1773         out.format("        set%s(%s);\n", Util.upperFirst(attribute.getFieldName()),
1774                 attribute.getFieldName());
1775         out.format("        return this;\n");
1776         out.format("    }\n\n");
1777     }
1778 
1779     private static String getDocumentation(MyIndependentAttribute attribute) {
1780         String doco = attribute.getExtensions().getDocumentationContent();
1781         if (doco == null)
1782             return "";
1783         else {
1784             doco = doco.trim();
1785             if (doco.endsWith(".") || doco.endsWith("?") || doco.endsWith("!"))
1786                 return doco;
1787             else
1788                 return doco.concat(".");
1789         }
1790     }
1791 
1792     private void writeStaticFinderMethods(PrintStream out, ClassInfo info) {
1793 
1794         out.format("    public static %s<%s> find(%s id) {\n", info.addType(Optional.class),
1795                 info.getJavaClassSimpleName(), info.addType(getIdType(info).getBase()),
1796                 getIdType(info).getClass().getSimpleName());
1797         // TODO return the found thing using current entity manager
1798         out.format("        if (Context.em()!=null) {\n");
1799         out.format("            return %s.fromNullable(Context.em().find(%s.class,id));\n",
1800                 info.addType(Optional.class), info.getJavaClassSimpleName());
1801         out.format("        } else {\n");
1802         out.format("            %s em = Context.createEntityManager();\n",
1803                 info.addType(EntityManager.class));
1804         out.format("            try {\n");
1805         out.format("                %s result = em.find(%s.class,id);\n",
1806                 info.getJavaClassSimpleName(), info.getJavaClassSimpleName());
1807         out.format("                return %s.fromNullable(result);\n",
1808                 info.addType(Optional.class));
1809         out.format("            } finally {\n");
1810         out.format("                em.close();\n");
1811         out.format("            }\n");
1812         // TODO do try finally em.close() so em not left open on error
1813         out.format("        }\n");
1814         out.format("    }\n\n");
1815 
1816         for (MyFind find : info.getFinders()) {
1817             jd(out, "Static finder method generated due to xuml-tools extension <b>Find</b>.",
1818                     "    ");
1819             StringBuffer findBy = new StringBuffer();
1820             for (MyIndependentAttribute attribute : find.getAttributes()) {
1821                 findBy.append(Util.upperFirst(attribute.getFieldName()));
1822             }
1823 
1824             out.format("    public static %s<%s> findBy%s(", info.addType(List.class),
1825                     info.getJavaClassSimpleName(), findBy.toString());
1826             {
1827                 boolean first = true;
1828                 for (MyIndependentAttribute attribute : find.getAttributes()) {
1829                     if (!first)
1830                         out.format(", ");
1831                     out.format("%s %s", info.addType(attribute.getType().getType()),
1832                             attribute.getFieldName());
1833                     first = false;
1834                 }
1835             }
1836             out.format(") {\n");
1837             out.format("        %s em = Context.createEntityManager();\n",
1838                     info.addType(EntityManager.class));
1839             out.format("        @%s(\"unchecked\")\n", info.addType(SuppressWarnings.class));
1840             out.format("        %s<%s> list = em.createQuery(\"select e from %s e where ",
1841                     info.addType(List.class), info.getJavaClassSimpleName(),
1842                     info.getJavaClassSimpleName());
1843             {
1844                 boolean first = true;
1845                 for (MyIndependentAttribute attribute : find.getAttributes()) {
1846                     if (!first)
1847                         out.format(" and ");
1848                     out.format("e.%s=:%s", attribute.getFieldName(), attribute.getFieldName());
1849                     first = false;
1850                 }
1851             }
1852             out.format("\")");
1853             for (MyIndependentAttribute attribute : find.getAttributes()) {
1854                 out.format("\n            .setParameter(\"%s\", %s)", attribute.getFieldName(),
1855                         attribute.getFieldName());
1856             }
1857             out.format("\n            .getResultList();\n");
1858             out.format("\n      em.close();\n");
1859             out.format("        return list;\n");
1860             out.format("    }\n\n");
1861 
1862             jd(out, "Static finder method generated due to xuml-tools extension <b>Find</b>.",
1863                     "    ");
1864 
1865             out.format("    public static %s<%s> findBy%s(%s em, ", info.addType(List.class),
1866                     info.getJavaClassSimpleName(), findBy.toString(),
1867                     info.addType(EntityManager.class));
1868             {
1869                 boolean first = true;
1870                 for (MyIndependentAttribute attribute : find.getAttributes()) {
1871                     if (!first)
1872                         out.format(", ");
1873                     out.format("%s %s", info.addType(attribute.getType().getType()),
1874                             attribute.getFieldName());
1875                     first = false;
1876                 }
1877             }
1878             out.format(") {\n");
1879             out.format("        @%s(\"unchecked\")\n", info.addType(SuppressWarnings.class));
1880             out.format("        %s<%s> list = em.createQuery(\"select e from %s e where ",
1881                     info.addType(List.class), info.getJavaClassSimpleName(),
1882                     info.getJavaClassSimpleName());
1883             {
1884                 boolean first = true;
1885                 for (MyIndependentAttribute attribute : find.getAttributes()) {
1886                     if (!first)
1887                         out.format(" and ");
1888                     out.format("e.%s=:%s", attribute.getFieldName(), attribute.getFieldName());
1889                     first = false;
1890                 }
1891             }
1892             out.format("\")");
1893             for (MyIndependentAttribute attribute : find.getAttributes()) {
1894                 out.format("\n            .setParameter(\"%s\", %s)", attribute.getFieldName(),
1895                         attribute.getFieldName());
1896             }
1897             out.format("\n            .getResultList();\n");
1898             out.format("        return list;\n");
1899             out.format("    }\n\n");
1900 
1901         }
1902     }
1903 
1904     private void writeClassClose(PrintStream out) {
1905         out.format("}");
1906     }
1907 
1908     private void writePackage(PrintStream out, ClassInfo info) {
1909         out.format("package %s;\n\n", info.getPackage());
1910     }
1911 
1912     private void writeImports(PrintStream out, ClassInfo info) {
1913         out.println(info.getImports(info.getClassFullName()));
1914     }
1915 
1916     // ///////////////////////////////////////
1917     // Utils
1918     // ///////////////////////////////////////
1919 
1920     private void jd(PrintStream out, String comment, String indent) {
1921         out.format("%s/**\n", indent);
1922         for (String line : comment.split("\n")) {
1923             out.format("%s * %s\n", indent, line);
1924         }
1925         out.format("%s */\n", indent);
1926     }
1927 
1928     private String getDelimited(Collection<String> items, String delimiter, String itemBefore,
1929             String itemAfter) {
1930         StringBuilder s = new StringBuilder();
1931         for (String item : items) {
1932             if (s.length() > 0)
1933                 s.append(delimiter);
1934             s.append(itemBefore);
1935             s.append(item);
1936             s.append(itemAfter);
1937         }
1938         return s.toString();
1939     }
1940 
1941     private String getCommaDelimitedQuoted(List<String> items) {
1942         return getDelimited(items, ",", "\"", "\"");
1943     }
1944 
1945     private void writeQueryMethods(PrintStream out, ClassInfo info) {
1946 
1947         out.format("    public static class Attribute {\n");
1948         for (MyIndependentAttribute member : info.getNonPrimaryIdIndependentAttributeMembers()) {
1949             MyType type = member.getType().getMyType();
1950             String fieldName = member.getFieldName();
1951             String fieldNameInQuery = fieldName;
1952             writeQueryField(out, info, type, fieldName, fieldNameInQuery);
1953         }
1954         for (MyIdAttribute member : info.getPrimaryIdAttributeMembers()) {
1955             MyType type = member.getType().getMyType();
1956             String fieldName = member.getFieldName();
1957             String fieldNameInQuery;
1958             if (hasEmbeddedId()) {
1959                 fieldNameInQuery = "id." + fieldName;
1960             } else {
1961                 fieldNameInQuery = fieldName;
1962             }
1963             writeQueryField(out, info, type, fieldName, fieldNameInQuery);
1964         }
1965         for (MyReferenceMember member : info.getReferenceMembers()) {
1966             writeQueryReferenceField(out, info, member);
1967         }
1968         out.format("    }\n\n");
1969         out.format("    public static %s<%s> select(%s<%s> where) {\n",
1970                 info.addType(SelectBuilder.class), info.getJavaClassSimpleName(),
1971                 info.addType(BooleanExpression.class), info.getJavaClassSimpleName());
1972         out.format(
1973                 "        return new %s<%s>(where).entityClass(%s.class).info(signaller.getInfo());\n",
1974                 info.addType(SelectBuilder.class), info.getJavaClassSimpleName(),
1975                 info.getJavaClassSimpleName());
1976         out.format("    }\n\n");
1977 
1978         out.format("    public static %s<%s> select() {\n", info.addType(SelectBuilder.class),
1979                 info.getJavaClassSimpleName());
1980         out.format("        return select(null);\n");
1981         out.format("    }\n\n");
1982 
1983     }
1984 
1985     private void writeQueryReferenceField(PrintStream out, ClassInfo info,
1986             MyReferenceMember member) {
1987         for (OtherId id : member.getOtherIds()) {
1988             writeQueryField(out, info, id.getType().getMyType(),
1989                     member.getFieldName() + "_" + id.getFieldName(),
1990                     member.getFieldName() + "." + id.getFieldName());
1991         }
1992     }
1993 
1994     private void writeQueryField(PrintStream out, ClassInfo info, MyType type, String fieldName,
1995             String fieldNameInQuery) {
1996         if (type == MyType.REAL || type == MyType.INTEGER) {
1997             out.format(
1998                     "        public static final %1$s<%3$s> %2$s = new %1$s<%3$s>(\n            new %4$s(\"%5$s\"));\n",
1999                     info.addType(NumericExpressionField.class), fieldName,
2000                     info.getJavaClassSimpleName(), Field.class.getName(), fieldNameInQuery);
2001         } else {
2002             out.format(
2003                     "        public static final %1$s<%3$s> %2$s = new %1$s<%3$s>(\n            new %4$s(\"%5$s\"));\n",
2004                     info.addType(StringExpressionField.class), fieldName,
2005                     info.getJavaClassSimpleName(), Field.class.getName(), fieldNameInQuery);
2006         }
2007     }
2008 }