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