Adjust to yangtools-2.0.0 changes
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.java.api.generator
9
10 import com.google.common.collect.ImmutableSortedSet
11 import java.util.ArrayList
12 import java.util.Arrays
13 import java.util.Collection
14 import java.util.Collections
15 import java.util.HashMap
16 import java.util.HashSet
17 import java.util.LinkedHashSet
18 import java.util.List
19 import java.util.Map
20 import java.util.Objects
21 import java.util.Set
22 import org.opendaylight.mdsal.binding.model.api.ConcreteType
23 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
24 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType
26 import org.opendaylight.mdsal.binding.model.api.MethodSignature
27 import org.opendaylight.mdsal.binding.model.api.Type
28 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
29 import org.opendaylight.mdsal.binding.model.util.Types
30 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
31 import org.opendaylight.yangtools.concepts.Builder
32 import org.opendaylight.yangtools.yang.binding.Augmentable
33 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
34 import org.opendaylight.yangtools.yang.binding.DataObject
35 import org.opendaylight.yangtools.yang.binding.Identifiable
36
37 /**
38  * Template for generating JAVA builder classes.
39  */
40
41 class BuilderTemplate extends BaseTemplate {
42
43     /**
44      * Constant with the name of the concrete method.
45      */
46     val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
47
48     /**
49      * Constant with the suffix for builder classes.
50      */
51     val static BUILDER = 'Builder'
52
53     /**
54      * Constant with the name of the BuilderFor interface
55      */
56     val static BUILDERFOR = Builder.simpleName;
57
58     /**
59      * Constant with suffix for the classes which are generated from the builder classes.
60      */
61     val static IMPL = 'Impl'
62
63     /**
64      * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
65      */
66     var GeneratedProperty augmentField
67
68     /**
69      * Set of class attributes (fields) which are derived from the getter methods names
70      */
71     val Set<GeneratedProperty> properties
72
73     private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
74
75     /**
76      * Constructs new instance of this class.
77      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
78      */
79     new(GeneratedType genType) {
80         super(genType)
81         this.properties = propertiesFromMethods(createMethods)
82         importMap.put(Builder.simpleName, Builder.package.name)
83     }
84
85     /**
86      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
87      * and all the methods of the implemented interfaces.
88      *
89      * @returns set of method signature instances
90      */
91     def private Set<MethodSignature> createMethods() {
92         val Set<MethodSignature> methods = new LinkedHashSet();
93         methods.addAll(type.methodDefinitions)
94         collectImplementedMethods(methods, type.implements)
95         val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
96
97         return sortedMethods
98     }
99
100     /**
101      * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
102      * and recursively their implemented interfaces.
103      *
104      * @param methods set of method signatures
105      * @param implementedIfcs list of implemented interfaces
106      */
107     def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
108         if (implementedIfcs === null || implementedIfcs.empty) {
109             return
110         }
111         for (implementedIfc : implementedIfcs) {
112             if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
113                 val ifc = implementedIfc as GeneratedType
114                 methods.addAll(ifc.methodDefinitions)
115                 collectImplementedMethods(methods, ifc.implements)
116             } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
117                 for (m : Augmentable.methods) {
118                     if (m.name == GET_AUGMENTATION_METHOD_NAME) {
119                         val fullyQualifiedName = m.returnType.name
120                         val pkg = fullyQualifiedName.package
121                         val name = fullyQualifiedName.name
122                         val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
123                         val refType = new ReferencedTypeImpl(pkg, name)
124                         val generic = new ReferencedTypeImpl(type.packageName, type.name)
125                         val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
126                         tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
127                         augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
128                     }
129                 }
130             }
131         }
132     }
133
134     /**
135      * Returns the first element of the list <code>elements</code>.
136      *
137      * @param list of elements
138      */
139     def private <E> first(List<E> elements) {
140         elements.get(0)
141     }
142
143     /**
144      * Returns the name of the package from <code>fullyQualifiedName</code>.
145      *
146      * @param fullyQualifiedName string with fully qualified type name (package + type)
147      * @return string with the package name
148      */
149     def private String getPackage(String fullyQualifiedName) {
150         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
151         return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
152     }
153
154     /**
155      * Returns the name of tye type from <code>fullyQualifiedName</code>
156      *
157      * @param fullyQualifiedName string with fully qualified type name (package + type)
158      * @return string with the name of the type
159      */
160     def private String getName(String fullyQualifiedName) {
161         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
162         return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
163     }
164
165     /**
166      * Creates set of generated property instances from getter <code>methods</code>.
167      *
168      * @param set of method signature instances which should be transformed to list of properties
169      * @return set of generated property instances which represents the getter <code>methods</code>
170      */
171     def private propertiesFromMethods(Collection<MethodSignature> methods) {
172         if (methods === null || methods.isEmpty()) {
173             return Collections.emptySet
174         }
175         val Set<GeneratedProperty> result = new LinkedHashSet
176         for (m : methods) {
177             val createdField = m.propertyFromGetter
178             if (createdField !== null) {
179                 result.add(createdField)
180             }
181         }
182         return result
183     }
184
185     /**
186      * Creates generated property instance from the getter <code>method</code> name and return type.
187      *
188      * @param method method signature from which is the method name and return type obtained
189      * @return generated property instance for the getter <code>method</code>
190      * @throws IllegalArgumentException<ul>
191      *  <li>if the <code>method</code> equals <code>null</code></li>
192      *  <li>if the name of the <code>method</code> equals <code>null</code></li>
193      *  <li>if the name of the <code>method</code> is empty</li>
194      *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
195      * </ul>
196      */
197     def private GeneratedProperty propertyFromGetter(MethodSignature method) {
198         if (method === null || method.name === null || method.name.empty || method.returnType === null) {
199             throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
200         }
201         var prefix = "get";
202         if (Types.BOOLEAN.equals(method.returnType)) {
203             prefix = "is";
204         }
205         if (method.name.startsWith(prefix)) {
206             val fieldName = method.getName().substring(prefix.length()).toFirstLower
207             val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
208             tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
209             return tmpGenTO.toInstance.properties.first
210         }
211     }
212
213     override isLocalInnerClass(String importedTypePackageName) {
214         // Builders do not have inner types
215         return false;
216     }
217
218     /**
219      * Template method which generates JAVA class body for builder class and for IMPL class.
220      *
221      * @return string with JAVA source code
222      */
223     override body() '''
224         «wrapToDocumentation(formatDataForJavaDoc(type))»
225         public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
226
227             «generateFields(false)»
228
229             «generateAugmentField(false)»
230
231             «generateConstructorsFromIfcs(type)»
232
233             «generateCopyConstructor(false)»
234
235             «generateMethodFieldsFrom(type)»
236
237             «generateGetters(false)»
238
239             «generateSetters»
240
241             @Override
242             public «type.name» build() {
243                 return new «type.name»«IMPL»(this);
244             }
245
246             private static final class «type.name»«IMPL» implements «type.name» {
247
248                 «implementedInterfaceGetter»
249
250                 «generateFields(true)»
251
252                 «generateAugmentField(true)»
253
254                 «generateCopyConstructor(true)»
255
256                 «generateGetters(true)»
257
258                 «generateHashCode()»
259
260                 «generateEquals()»
261
262                 «generateToString(properties)»
263             }
264
265         }
266     '''
267
268     /**
269      * Generate default constructor and constructor for every implemented interface from uses statements.
270      */
271     def private generateConstructorsFromIfcs(Type type) '''
272         public «type.name»«BUILDER»() {
273         }
274         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
275             «val ifc = type as GeneratedType»
276             «FOR impl : ifc.implements»
277                 «generateConstructorFromIfc(impl)»
278             «ENDFOR»
279         «ENDIF»
280     '''
281
282     /**
283      * Generate constructor with argument of given type.
284      */
285     def private Object generateConstructorFromIfc(Type impl) '''
286         «IF (impl instanceof GeneratedType)»
287             «IF !(impl.methodDefinitions.empty)»
288                 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
289                     «printConstructorPropertySetter(impl)»
290                 }
291             «ENDIF»
292             «FOR implTypeImplement : impl.implements»
293                 «generateConstructorFromIfc(implTypeImplement)»
294             «ENDFOR»
295         «ENDIF»
296     '''
297
298     def private Object printConstructorPropertySetter(Type implementedIfc) '''
299         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
300             «val ifc = implementedIfc as GeneratedType»
301             «FOR getter : ifc.methodDefinitions»
302                 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
303             «ENDFOR»
304             «FOR impl : ifc.implements»
305                 «printConstructorPropertySetter(impl)»
306             «ENDFOR»
307         «ENDIF»
308     '''
309
310     /**
311      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
312      */
313     def private generateMethodFieldsFrom(Type type) '''
314         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
315             «val ifc = type as GeneratedType»
316             «IF ifc.hasImplementsFromUses»
317                 «val List<Type> done = ifc.getBaseIfcs»
318                 «generateMethodFieldsFromComment(ifc)»
319                 public void fieldsFrom(«DataObject.importedName» arg) {
320                     boolean isValidArg = false;
321                     «FOR impl : ifc.getAllIfcs»
322                         «generateIfCheck(impl, done)»
323                     «ENDFOR»
324                     if (!isValidArg) {
325                         throw new IllegalArgumentException(
326                           "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
327                           "but was: " + arg
328                         );
329                     }
330                 }
331             «ENDIF»
332         «ENDIF»
333     '''
334
335     def private generateMethodFieldsFromComment(GeneratedType type) '''
336         /**
337          *Set fields from given grouping argument. Valid argument is instance of one of following types:
338          * <ul>
339          «FOR impl : type.getAllIfcs»
340          * <li>«impl.fullyQualifiedName»</li>
341          «ENDFOR»
342          * </ul>
343          *
344          * @param arg grouping object
345          * @throws IllegalArgumentException if given argument is none of valid types
346         */
347     '''
348
349     /**
350      * Method is used to find out if given type implements any interface from uses.
351      */
352     def boolean hasImplementsFromUses(GeneratedType type) {
353         var i = 0
354         for (impl : type.getAllIfcs) {
355             if ((impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)) {
356                 i = i + 1
357             }
358         }
359         return i > 0
360     }
361
362     def private generateIfCheck(Type impl, List<Type> done) '''
363         «IF (impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)»
364             «val implType = impl as GeneratedType»
365             if (arg instanceof «implType.fullyQualifiedName») {
366                 «printPropertySetter(implType)»
367                 isValidArg = true;
368             }
369         «ENDIF»
370     '''
371
372     def private printPropertySetter(Type implementedIfc) '''
373         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
374         «val ifc = implementedIfc as GeneratedType»
375         «FOR getter : ifc.methodDefinitions»
376             this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
377         «ENDFOR»
378         «ENDIF»
379     '''
380
381     private def List<Type> getBaseIfcs(GeneratedType type) {
382         val List<Type> baseIfcs = new ArrayList();
383         for (ifc : type.implements) {
384             if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
385                 baseIfcs.add(ifc)
386             }
387         }
388         return baseIfcs
389     }
390
391     private def Set<Type> getAllIfcs(Type type) {
392         val Set<Type> baseIfcs = new HashSet()
393         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
394             val ifc = type as GeneratedType
395             for (impl : ifc.implements) {
396                 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
397                     baseIfcs.add(impl)
398                 }
399                 baseIfcs.addAll(impl.getAllIfcs)
400             }
401         }
402         return baseIfcs
403     }
404
405     private def List<String> toListOfNames(Collection<Type> types) {
406         val List<String> names = new ArrayList
407         for (type : types) {
408             names.add(type.fullyQualifiedName)
409         }
410         return names
411     }
412
413     /**
414      * Template method which generates class attributes.
415      *
416      * @param boolean value which specify whether field is|isn't final
417      * @return string with class attributes and their types
418      */
419     def private generateFields(boolean _final) '''
420         «IF properties !== null»
421             «FOR f : properties»
422                 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
423             «ENDFOR»
424         «ENDIF»
425     '''
426
427     def private generateAugmentField(boolean isPrivate) '''
428         «IF augmentField !== null»
429             «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
430         «ENDIF»
431     '''
432
433     /**
434      * Template method which generates setter methods
435      *
436      * @return string with the setter methods
437      */
438     def private generateSetters() '''
439         «FOR field : properties SEPARATOR '\n'»
440              «/* FIXME: generate checkers as simple blocks and embed them directly in setters  */»
441              «val restrictions = field.returnType.restrictions»
442              «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
443                     «IF restrictions.rangeConstraint.present»
444                         «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
445                         «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get)»
446
447                     «ENDIF»
448                     «IF restrictions.lengthConstraint.present»
449                     «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get)»
450
451                     «ENDIF»
452             «ENDIF»
453             public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
454             «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
455                 «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
456                 if (value != null) {
457                     «IF restrictions.rangeConstraint.present»
458                         «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
459                         «IF field.returnType instanceof ConcreteType»
460                             «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
461                         «ELSE»
462                             «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
463                         «ENDIF»
464                     «ENDIF»
465                     «IF restrictions.lengthConstraint.present»
466                         «IF field.returnType instanceof ConcreteType»
467                             «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
468                          «ELSE»
469                             «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
470                         «ENDIF»
471                     «ENDIF»
472                 }
473                 «ENDIF»
474             «ENDIF»
475                 this.«field.fieldName» = value;
476                 return this;
477             }
478         «ENDFOR»
479         «IF augmentField !== null»
480
481             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
482                 if (augmentationValue == null) {
483                     return remove«augmentField.name.toFirstUpper»(augmentationType);
484                 }
485
486                 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
487                     this.«augmentField.name» = new «HashMap.importedName»<>();
488                 }
489
490                 this.«augmentField.name».put(augmentationType, augmentationValue);
491                 return this;
492             }
493
494             public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
495                 if (this.«augmentField.name» instanceof «HashMap.importedName») {
496                     this.«augmentField.name».remove(augmentationType);
497                 }
498                 return this;
499             }
500         «ENDIF»
501     '''
502
503     def private CharSequence generateCopyConstructor(boolean impl) '''
504         «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
505             «val allProps = new ArrayList(properties)»
506             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
507             «val keyType = type.getKey»
508             «IF isList && keyType !== null»
509                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
510                 «Collections.sort(keyProps,
511                     [ p1, p2 |
512                         return p1.name.compareTo(p2.name)
513                     ])
514                 »
515                 «FOR field : keyProps»
516                     «removeProperty(allProps, field.name)»
517                 «ENDFOR»
518                 «removeProperty(allProps, "key")»
519                 if (base.getKey() == null) {
520                     this._key = new «keyType.importedName»(
521                         «FOR keyProp : keyProps SEPARATOR ", "»
522                             base.«keyProp.getterMethodName»()
523                         «ENDFOR»
524                     );
525                     «FOR field : keyProps»
526                         this.«field.fieldName» = base.«field.getterMethodName»();
527                     «ENDFOR»
528                 } else {
529                     this._key = base.getKey();
530                     «FOR field : keyProps»
531                            this.«field.fieldName» = _key.«field.getterMethodName»();
532                     «ENDFOR»
533                 }
534             «ENDIF»
535             «FOR field : allProps»
536                 this.«field.fieldName» = base.«field.getterMethodName»();
537             «ENDFOR»
538             «IF augmentField !== null»
539                 «IF impl»
540                     switch (base.«augmentField.name».size()) {
541                     case 0:
542                         this.«augmentField.name» = «Collections.importedName».emptyMap();
543                         break;
544                     case 1:
545                         final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
546                         this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
547                         break;
548                     default :
549                         this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
550                     }
551                 «ELSE»
552                     if (base instanceof «type.name»«IMPL») {
553                         «type.name»«IMPL» impl = («type.name»«IMPL») base;
554                         if (!impl.«augmentField.name».isEmpty()) {
555                             this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
556                         }
557                     } else if (base instanceof «AugmentationHolder.importedName») {
558                         @SuppressWarnings("unchecked")
559                         «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
560                         if (!casted.augmentations().isEmpty()) {
561                             this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
562                         }
563                     }
564                 «ENDIF»
565             «ENDIF»
566         }
567     '''
568
569     private def boolean implementsIfc(GeneratedType type, Type impl) {
570         for (Type ifc : type.implements) {
571             if (ifc.equals(impl)) {
572                 return true;
573             }
574         }
575         return false;
576     }
577
578     private def Type getKey(GeneratedType type) {
579         for (m : type.methodDefinitions) {
580             if ("getKey".equals(m.name)) {
581                 return m.returnType;
582             }
583         }
584         return null;
585     }
586
587     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
588         var GeneratedProperty toRemove = null
589         for (p : props) {
590             if (p.name.equals(name)) {
591                 toRemove = p;
592             }
593         }
594         if (toRemove !== null) {
595             props.remove(toRemove);
596         }
597     }
598
599     /**
600      * Template method which generate getter methods for IMPL class.
601      *
602      * @return string with getter methods
603      */
604     def private generateGetters(boolean addOverride) '''
605         «IF !properties.empty»
606             «FOR field : properties SEPARATOR '\n'»
607                 «IF addOverride»@Override«ENDIF»
608                 «field.getterMethod»
609             «ENDFOR»
610         «ENDIF»
611         «IF augmentField !== null»
612
613             @SuppressWarnings("unchecked")
614             «IF addOverride»@Override«ENDIF»
615             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
616                 if (augmentationType == null) {
617                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
618                 }
619                 return (E) «augmentField.name».get(augmentationType);
620             }
621         «ENDIF»
622     '''
623
624     /**
625      * Template method which generates the method <code>hashCode()</code>.
626      *
627      * @return string with the <code>hashCode()</code> method definition in JAVA format
628      */
629     def protected generateHashCode() '''
630         «IF !properties.empty || augmentField !== null»
631             private int hash = 0;
632             private volatile boolean hashValid = false;
633
634             @Override
635             public int hashCode() {
636                 if (hashValid) {
637                     return hash;
638                 }
639
640                 final int prime = 31;
641                 int result = 1;
642                 «FOR property : properties»
643                     «IF property.returnType.name.contains("[")»
644                     result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
645                     «ELSE»
646                     result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
647                     «ENDIF»
648                 «ENDFOR»
649                 «IF augmentField !== null»
650                     result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
651                 «ENDIF»
652
653                 hash = result;
654                 hashValid = true;
655                 return result;
656             }
657         «ENDIF»
658     '''
659
660     /**
661      * Template method which generates the method <code>equals()</code>.
662      *
663      * @return string with the <code>equals()</code> method definition in JAVA format
664      */
665     def protected generateEquals() '''
666         «IF !properties.empty || augmentField !== null»
667             @Override
668             public boolean equals(«Object.importedName» obj) {
669                 if (this == obj) {
670                     return true;
671                 }
672                 if (!(obj instanceof «DataObject.importedName»)) {
673                     return false;
674                 }
675                 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
676                     return false;
677                 }
678                 «type.importedName» other = («type.importedName»)obj;
679                 «FOR property : properties»
680                     «val fieldName = property.fieldName»
681                     «IF property.returnType.name.contains("[")»
682                     if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
683                     «ELSE»
684                     if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
685                     «ENDIF»
686                         return false;
687                     }
688                 «ENDFOR»
689                 «IF augmentField !== null»
690                     if (getClass() == obj.getClass()) {
691                         // Simple case: we are comparing against self
692                         «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
693                         «val fieldName = augmentField.name»
694                         if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
695                             return false;
696                         }
697                     } else {
698                         // Hard case: compare our augments with presence there...
699                         for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
700                             if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
701                                 return false;
702                             }
703                         }
704                         // .. and give the other one the chance to do the same
705                         if (!obj.equals(this)) {
706                             return false;
707                         }
708                     }
709                 «ENDIF»
710                 return true;
711             }
712         «ENDIF»
713     '''
714
715     def override generateToString(Collection<GeneratedProperty> properties) '''
716         «IF !(properties === null)»
717             @Override
718             public «String.importedName» toString() {
719                 «String.importedName» name = "«type.name» [";
720                 «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
721                 «FOR property : properties SEPARATOR "\n    builder.append(\", \");\n}" AFTER "    }\n"»
722                     if («property.fieldName» != null) {
723                         builder.append("«property.fieldName»=");
724                         «IF property.returnType.name.contains("[")»
725                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
726                         «ELSE»
727                             builder.append(«property.fieldName»);
728                         «ENDIF»
729                 «ENDFOR»
730                 «IF augmentField !== null»
731                     «IF !properties.empty»
732                 «««Append comma separator only if it's not there already from previous operation»»»
733 final int builderLength = builder.length();
734                     final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
735                     if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
736                         builder.append(", ");
737                     }
738                     «ENDIF»
739                     builder.append("«augmentField.name»=");
740                     builder.append(«augmentField.name».values());«"\n"»
741                     return builder.append(']').toString();
742                 «ELSE»
743                     «IF properties.empty»
744                     return builder.append(']').toString();
745                     «ELSE»
746             return builder.append(']').toString();
747                     «ENDIF»
748                 «ENDIF»
749             }
750         «ENDIF»
751     '''
752
753     def implementedInterfaceGetter() '''
754     @Override
755     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
756         return «type.importedName».class;
757     }
758     '''
759
760     private def createDescription(GeneratedType type) {
761         return '''
762         Class that builds {@link «type.importedName»} instances.
763
764         @see «type.importedName»
765     '''
766     }
767
768     override def protected String formatDataForJavaDoc(GeneratedType type) {
769         val typeDescription = createDescription(type)
770
771         return '''
772             «IF !typeDescription.nullOrEmpty»
773             «typeDescription»
774             «ENDIF»
775         '''.toString
776     }
777 }
778