Revert "Bug 3147 - Binding spec v1: auto generated code by YANGTOOLS could"
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / yangtools / sal / 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.yangtools.sal.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.yangtools.binding.generator.util.ReferencedTypeImpl
23 import org.opendaylight.yangtools.binding.generator.util.Types
24 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
25 import org.opendaylight.yangtools.concepts.Builder
26 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
28 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
29 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
30 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
31 import org.opendaylight.yangtools.sal.binding.model.api.Type
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     /**
214      * Template method which generates JAVA class body for builder class and for IMPL class.
215      *
216      * @return string with JAVA source code
217      */
218     override body() '''
219         «wrapToDocumentation(formatDataForJavaDoc(type))»
220         public class «type.name»«BUILDER» implements «BUILDERFOR» <«type.importedName»> {
221
222             «generateFields(false)»
223
224             «generateAugmentField(false)»
225
226             «generateConstructorsFromIfcs(type)»
227
228             «generateCopyConstructor(false)»
229
230             «generateMethodFieldsFrom(type)»
231
232             «generateGetters(false)»
233
234             «generateSetters»
235
236             public «type.name» build() {
237                 return new «type.name»«IMPL»(this);
238             }
239
240             private static final class «type.name»«IMPL» implements «type.name» {
241
242                 «implementedInterfaceGetter»
243
244                 «generateFields(true)»
245
246                 «generateAugmentField(true)»
247
248                 «generateCopyConstructor(true)»
249
250                 «generateGetters(true)»
251
252                 «generateHashCode()»
253
254                 «generateEquals()»
255
256                 «generateToString(properties)»
257             }
258
259         }
260     '''
261
262     /**
263      * Generate default constructor and constructor for every implemented interface from uses statements.
264      */
265     def private generateConstructorsFromIfcs(Type type) '''
266         public «type.name»«BUILDER»() {
267         }
268         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
269             «val ifc = type as GeneratedType»
270             «FOR impl : ifc.implements»
271                 «generateConstructorFromIfc(impl)»
272             «ENDFOR»
273         «ENDIF»
274     '''
275
276     /**
277      * Generate constructor with argument of given type.
278      */
279     def private Object generateConstructorFromIfc(Type impl) '''
280         «IF (impl instanceof GeneratedType)»
281             «IF !(impl.methodDefinitions.empty)»
282                 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
283                     «printConstructorPropertySetter(impl)»
284                 }
285             «ENDIF»
286             «FOR implTypeImplement : impl.implements»
287                 «generateConstructorFromIfc(implTypeImplement)»
288             «ENDFOR»
289         «ENDIF»
290     '''
291
292     def private Object printConstructorPropertySetter(Type implementedIfc) '''
293         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
294             «val ifc = implementedIfc as GeneratedType»
295             «FOR getter : ifc.methodDefinitions»
296                 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
297             «ENDFOR»
298             «FOR impl : ifc.implements»
299                 «printConstructorPropertySetter(impl)»
300             «ENDFOR»
301         «ENDIF»
302     '''
303
304     /**
305      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
306      */
307     def private generateMethodFieldsFrom(Type type) '''
308         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
309             «val ifc = type as GeneratedType»
310             «IF ifc.hasImplementsFromUses»
311                 «val List<Type> done = ifc.getBaseIfcs»
312                 «generateMethodFieldsFromComment(ifc)»
313                 public void fieldsFrom(«DataObject.importedName» arg) {
314                     boolean isValidArg = false;
315                     «FOR impl : ifc.getAllIfcs»
316                         «generateIfCheck(impl, done)»
317                     «ENDFOR»
318                     if (!isValidArg) {
319                         throw new IllegalArgumentException(
320                           "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
321                           "but was: " + arg
322                         );
323                     }
324                 }
325             «ENDIF»
326         «ENDIF»
327     '''
328
329     def private generateMethodFieldsFromComment(GeneratedType type) '''
330         /**
331          *Set fields from given grouping argument. Valid argument is instance of one of following types:
332          * <ul>
333          «FOR impl : type.getAllIfcs»
334          * <li>«impl.fullyQualifiedName»</li>
335          «ENDFOR»
336          * </ul>
337          *
338          * @param arg grouping object
339          * @throws IllegalArgumentException if given argument is none of valid types
340         */
341     '''
342
343     /**
344      * Method is used to find out if given type implements any interface from uses.
345      */
346     def boolean hasImplementsFromUses(GeneratedType type) {
347         var i = 0
348         for (impl : type.getAllIfcs) {
349             if ((impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)) {
350                 i = i + 1
351             }
352         }
353         return i > 0
354     }
355
356     def private generateIfCheck(Type impl, List<Type> done) '''
357         «IF (impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)»
358             «val implType = impl as GeneratedType»
359             if (arg instanceof «implType.fullyQualifiedName») {
360                 «printPropertySetter(implType)»
361                 isValidArg = true;
362             }
363         «ENDIF»
364     '''
365
366     def private printPropertySetter(Type implementedIfc) '''
367         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
368         «val ifc = implementedIfc as GeneratedType»
369         «FOR getter : ifc.methodDefinitions»
370             this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
371         «ENDFOR»
372         «ENDIF»
373     '''
374
375     private def List<Type> getBaseIfcs(GeneratedType type) {
376         val List<Type> baseIfcs = new ArrayList();
377         for (ifc : type.implements) {
378             if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
379                 baseIfcs.add(ifc)
380             }
381         }
382         return baseIfcs
383     }
384
385     private def Set<Type> getAllIfcs(Type type) {
386         val Set<Type> baseIfcs = new HashSet()
387         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
388             val ifc = type as GeneratedType
389             for (impl : ifc.implements) {
390                 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
391                     baseIfcs.add(impl)
392                 }
393                 baseIfcs.addAll(impl.getAllIfcs)
394             }
395         }
396         return baseIfcs
397     }
398
399     private def List<String> toListOfNames(Collection<Type> types) {
400         val List<String> names = new ArrayList
401         for (type : types) {
402             names.add(type.fullyQualifiedName)
403         }
404         return names
405     }
406
407     /**
408      * Template method which generates class attributes.
409      *
410      * @param boolean value which specify whether field is|isn't final
411      * @return string with class attributes and their types
412      */
413     def private generateFields(boolean _final) '''
414         «IF properties !== null»
415             «FOR f : properties»
416                 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
417             «ENDFOR»
418         «ENDIF»
419     '''
420
421     def private generateAugmentField(boolean isPrivate) '''
422         «IF augmentField != null»
423             «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
424         «ENDIF»
425     '''
426
427     /**
428      * Template method which generates setter methods
429      *
430      * @return string with the setter methods
431      */
432     def private generateSetters() '''
433         «FOR field : properties SEPARATOR '\n'»
434              «/* FIXME: generate checkers as simple blocks and embed them directly in setters  */»
435              «val restrictions = field.returnType.restrictions»
436              «IF !(field.returnType instanceof GeneratedType) && restrictions != null»
437                     «IF !restrictions.rangeConstraints.nullOrEmpty»
438                         «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
439                         «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraints)»
440
441                     «ENDIF»
442                     «IF !restrictions.lengthConstraints.nullOrEmpty»
443                     «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraints)»
444
445                     «ENDIF»
446             «ENDIF»
447             public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
448             «IF !(field.returnType instanceof GeneratedType) && restrictions != null»
449                 «IF restrictions != null && (!restrictions.rangeConstraints.nullOrEmpty || !restrictions.lengthConstraints.nullOrEmpty)»
450                 if (value != null) {
451                     «IF !restrictions.rangeConstraints.nullOrEmpty»
452                         «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
453                         «IF field.returnType instanceof ConcreteType»
454                             «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
455                         «ELSE»
456                             «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
457                         «ENDIF»
458                     «ENDIF»
459                     «IF !restrictions.lengthConstraints.nullOrEmpty»
460                         «IF field.returnType instanceof ConcreteType»
461                             «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
462                          «ELSE»
463                             «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
464                         «ENDIF»
465                     «ENDIF»
466                 }
467                 «ENDIF»
468             «ENDIF»
469                 this.«field.fieldName» = value;
470                 return this;
471             }
472         «ENDFOR»
473         «IF augmentField != null»
474
475             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
476                 if (augmentation == null) {
477                     return remove«augmentField.name.toFirstUpper»(augmentationType);
478                 }
479
480                 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
481                     this.«augmentField.name» = new «HashMap.importedName»<>();
482                 }
483
484                 this.«augmentField.name».put(augmentationType, augmentation);
485                 return this;
486             }
487
488             public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
489                 if (this.«augmentField.name» instanceof «HashMap.importedName») {
490                     this.«augmentField.name».remove(augmentationType);
491                 }
492                 return this;
493             }
494         «ENDIF»
495     '''
496
497     def private CharSequence generateCopyConstructor(boolean impl) '''
498         «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
499             «val allProps = new ArrayList(properties)»
500             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
501             «val keyType = type.getKey»
502             «IF isList && keyType != null»
503                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
504                 «Collections.sort(keyProps,
505                     [ p1, p2 |
506                         return p1.name.compareTo(p2.name)
507                     ])
508                 »
509                 «FOR field : keyProps»
510                     «removeProperty(allProps, field.name)»
511                 «ENDFOR»
512                 «removeProperty(allProps, "key")»
513                 if (base.getKey() == null) {
514                     this._key = new «keyType.importedName»(
515                         «FOR keyProp : keyProps SEPARATOR ", "»
516                             base.«keyProp.getterMethodName»()
517                         «ENDFOR»
518                     );
519                     «FOR field : keyProps»
520                         this.«field.fieldName» = base.«field.getterMethodName»();
521                     «ENDFOR»
522                 } else {
523                     this._key = base.getKey();
524                     «FOR field : keyProps»
525                            this.«field.fieldName» = _key.«field.getterMethodName»();
526                     «ENDFOR»
527                 }
528             «ENDIF»
529             «FOR field : allProps»
530                 this.«field.fieldName» = base.«field.getterMethodName»();
531             «ENDFOR»
532             «IF augmentField != null»
533                 «IF impl»
534                     switch (base.«augmentField.name».size()) {
535                     case 0:
536                         this.«augmentField.name» = «Collections.importedName».emptyMap();
537                         break;
538                     case 1:
539                         final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
540                         this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
541                         break;
542                     default :
543                         this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
544                     }
545                 «ELSE»
546                     if (base instanceof «type.name»«IMPL») {
547                         «type.name»«IMPL» impl = («type.name»«IMPL») base;
548                         if (!impl.«augmentField.name».isEmpty()) {
549                             this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
550                         }
551                     } else if (base instanceof «AugmentationHolder.importedName») {
552                         @SuppressWarnings("unchecked")
553                         «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
554                         if (!casted.augmentations().isEmpty()) {
555                             this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
556                         }
557                     }
558                 «ENDIF»
559             «ENDIF»
560         }
561     '''
562
563     private def boolean implementsIfc(GeneratedType type, Type impl) {
564         for (Type ifc : type.implements) {
565             if (ifc.equals(impl)) {
566                 return true;
567             }
568         }
569         return false;
570     }
571
572     private def Type getKey(GeneratedType type) {
573         for (m : type.methodDefinitions) {
574             if ("getKey".equals(m.name)) {
575                 return m.returnType;
576             }
577         }
578         return null;
579     }
580
581     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
582         var GeneratedProperty toRemove = null
583         for (p : props) {
584             if (p.name.equals(name)) {
585                 toRemove = p;
586             }
587         }
588         if (toRemove != null) {
589             props.remove(toRemove);
590         }
591     }
592
593     /**
594      * Template method which generate getter methods for IMPL class.
595      *
596      * @return string with getter methods
597      */
598     def private generateGetters(boolean addOverride) '''
599         «IF !properties.empty»
600             «FOR field : properties SEPARATOR '\n'»
601                 «IF addOverride»@Override«ENDIF»
602                 «field.getterMethod»
603             «ENDFOR»
604         «ENDIF»
605         «IF augmentField != null»
606
607             @SuppressWarnings("unchecked")
608             «IF addOverride»@Override«ENDIF»
609             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
610                 if (augmentationType == null) {
611                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
612                 }
613                 return (E) «augmentField.name».get(augmentationType);
614             }
615         «ENDIF»
616     '''
617
618     /**
619      * Template method which generates the method <code>hashCode()</code>.
620      *
621      * @return string with the <code>hashCode()</code> method definition in JAVA format
622      */
623     def protected generateHashCode() '''
624         «IF !properties.empty || augmentField != null»
625             private int hash = 0;
626             private volatile boolean hashValid = false;
627
628             @Override
629             public int hashCode() {
630                 if (hashValid) {
631                     return hash;
632                 }
633
634                 final int prime = 31;
635                 int result = 1;
636                 «FOR property : properties»
637                     «IF property.returnType.name.contains("[")»
638                     result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
639                     «ELSE»
640                     result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
641                     «ENDIF»
642                 «ENDFOR»
643                 «IF augmentField != null»
644                     result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
645                 «ENDIF»
646
647                 hash = result;
648                 hashValid = true;
649                 return result;
650             }
651         «ENDIF»
652     '''
653
654     /**
655      * Template method which generates the method <code>equals()</code>.
656      *
657      * @return string with the <code>equals()</code> method definition in JAVA format
658      */
659     def protected generateEquals() '''
660         «IF !properties.empty || augmentField != null»
661             @Override
662             public boolean equals(«Object.importedName» obj) {
663                 if (this == obj) {
664                     return true;
665                 }
666                 if (!(obj instanceof «DataObject.importedName»)) {
667                     return false;
668                 }
669                 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
670                     return false;
671                 }
672                 «type.importedName» other = («type.importedName»)obj;
673                 «FOR property : properties»
674                     «val fieldName = property.fieldName»
675                     «IF property.returnType.name.contains("[")»
676                     if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
677                     «ELSE»
678                     if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
679                     «ENDIF»
680                         return false;
681                     }
682                 «ENDFOR»
683                 «IF augmentField != null»
684                     if (getClass() == obj.getClass()) {
685                         // Simple case: we are comparing against self
686                         «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
687                         «val fieldName = augmentField.name»
688                         if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
689                             return false;
690                         }
691                     } else {
692                         // Hard case: compare our augments with presence there...
693                         for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
694                             if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
695                                 return false;
696                             }
697                         }
698                         // .. and give the other one the chance to do the same
699                         if (!obj.equals(this)) {
700                             return false;
701                         }
702                     }
703                 «ENDIF»
704                 return true;
705             }
706         «ENDIF»
707     '''
708
709     def override generateToString(Collection<GeneratedProperty> properties) '''
710         «IF !(properties === null)»
711             @Override
712             public «String.importedName» toString() {
713                 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
714                 boolean first = true;
715
716                 «FOR property : properties»
717                     if («property.fieldName» != null) {
718                         if (first) {
719                             first = false;
720                         } else {
721                             builder.append(", ");
722                         }
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                      }
730                 «ENDFOR»
731                 «IF augmentField != null»
732                     if (first) {
733                         first = false;
734                     } else {
735                         builder.append(", ");
736                     }
737                     builder.append("«augmentField.name»=");
738                     builder.append(«augmentField.name».values());
739                 «ENDIF»
740                 return builder.append(']').toString();
741             }
742         «ENDIF»
743     '''
744
745     def implementedInterfaceGetter() '''
746     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
747         return «type.importedName».class;
748     }
749     '''
750
751     private def createDescription(GeneratedType type) {
752         return '''
753         Class that builds {@link «type.importedName»} instances.
754
755         @see «type.importedName»
756     '''
757     }
758
759     override def protected String formatDataForJavaDoc(GeneratedType type) {
760         val typeDescription = createDescription(type)
761
762         return '''
763             «IF !typeDescription.nullOrEmpty»
764             «typeDescription»
765             «ENDIF»
766         '''.toString
767     }
768 }
769