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