BUG-1485: deprecate public static range/length methods
[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     def private generateLengthMethod(String methodName, Type type, String className, String varName) '''
536         «val Restrictions restrictions = type.restrictions»
537         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
538             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
539             /**
540              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
541              */
542             @Deprecated
543             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
544                 «IF numberClass.equals(typeof(BigDecimal))»
545                     «lengthBody(restrictions, numberClass, className, varName)»
546                 «ELSE»
547                     «lengthBody(restrictions, typeof(BigInteger), className, varName)»
548                 «ENDIF»
549             }
550         «ENDIF»
551     '''
552
553     def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
554         if («varName» == null) {
555             synchronized («className».class) {
556                 if («varName» == null) {
557                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
558                     «FOR r : restrictions.lengthConstraints»
559                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
560                     «ENDFOR»
561                     «varName» = builder.build();
562                 }
563             }
564         }
565         return «varName»;
566     '''
567
568     def private generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
569         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
570             «val number = returnType.importedNumber»
571             /**
572              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
573              */
574             @Deprecated
575             public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
576                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
577                     «rangeBody(restrictions, BigDecimal, className, varName)»
578                 «ELSE»
579                     «rangeBody(restrictions, BigInteger, className, varName)»
580                 «ENDIF»
581             }
582         «ENDIF»
583     '''
584
585     def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
586         if («varName» == null) {
587             synchronized («className».class) {
588                 if («varName» == null) {
589                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
590                     «FOR r : restrictions.rangeConstraints»
591                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
592                     «ENDFOR»
593                     «varName» = builder.build();
594                 }
595             }
596         }
597         return «varName»;
598     '''
599
600     def private CharSequence generateCopyConstructor(boolean impl) '''
601         «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
602             «val allProps = new ArrayList(properties)»
603             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
604             «val keyType = type.getKey»
605             «IF isList && keyType != null»
606                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
607                 «Collections.sort(keyProps,
608                     [ p1, p2 |
609                         return p1.name.compareTo(p2.name)
610                     ])
611                 »
612                 «FOR field : keyProps»
613                     «removeProperty(allProps, field.name)»
614                 «ENDFOR»
615                 «removeProperty(allProps, "key")»
616                 if (base.getKey() == null) {
617                     this._key = new «keyType.importedName»(
618                         «FOR keyProp : keyProps SEPARATOR ", "»
619                             base.«keyProp.getterMethodName»()
620                         «ENDFOR»
621                     );
622                     «FOR field : keyProps»
623                         this.«field.fieldName» = base.«field.getterMethodName»();
624                     «ENDFOR»
625                 } else {
626                     this._key = base.getKey();
627                     «FOR field : keyProps»
628                            this.«field.fieldName» = _key.«field.getterMethodName»();
629                     «ENDFOR»
630                 }
631             «ENDIF»
632             «FOR field : allProps»
633                 this.«field.fieldName» = base.«field.getterMethodName»();
634             «ENDFOR»
635             «IF augmentField != null»
636                 «IF impl»
637                     switch (base.«augmentField.name».size()) {
638                     case 0:
639                         this.«augmentField.name» = «Collections.importedName».emptyMap();
640                         break;
641                     case 1:
642                         final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
643                         this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
644                         break;
645                     default :
646                         this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
647                     }
648                 «ELSE»
649                     if (base instanceof «type.name»«IMPL») {
650                         «type.name»«IMPL» impl = («type.name»«IMPL») base;
651                         if (!impl.«augmentField.name».isEmpty()) {
652                             this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
653                         }
654                     } else if (base instanceof «AugmentationHolder.importedName») {
655                         @SuppressWarnings("unchecked")
656                         «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
657                         if (!casted.augmentations().isEmpty()) {
658                             this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
659                         }
660                     }
661                 «ENDIF»
662             «ENDIF»
663         }
664     '''
665
666     private def boolean implementsIfc(GeneratedType type, Type impl) {
667         for (Type ifc : type.implements) {
668             if (ifc.equals(impl)) {
669                 return true;
670             }
671         }
672         return false;
673     }
674
675     private def Type getKey(GeneratedType type) {
676         for (m : type.methodDefinitions) {
677             if ("getKey".equals(m.name)) {
678                 return m.returnType;
679             }
680         }
681         return null;
682     }
683
684     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
685         var GeneratedProperty toRemove = null
686         for (p : props) {
687             if (p.name.equals(name)) {
688                 toRemove = p;
689             }
690         }
691         if (toRemove != null) {
692             props.remove(toRemove);
693         }
694     }
695
696     /**
697      * Template method which generate getter methods for IMPL class.
698      *
699      * @return string with getter methods
700      */
701     def private generateGetters(boolean addOverride) '''
702         «IF !properties.empty»
703             «FOR field : properties SEPARATOR '\n'»
704                 «IF addOverride»@Override«ENDIF»
705                 «field.getterMethod»
706             «ENDFOR»
707         «ENDIF»
708         «IF augmentField != null»
709
710             @SuppressWarnings("unchecked")
711             «IF addOverride»@Override«ENDIF»
712             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
713                 if (augmentationType == null) {
714                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
715                 }
716                 return (E) «augmentField.name».get(augmentationType);
717             }
718         «ENDIF»
719     '''
720
721     /**
722      * Template method which generates the method <code>hashCode()</code>.
723      *
724      * @return string with the <code>hashCode()</code> method definition in JAVA format
725      */
726     def protected generateHashCode() '''
727         «IF !properties.empty || augmentField != null»
728             @Override
729             public int hashCode() {
730                 final int prime = 31;
731                 int result = 1;
732                 «FOR property : properties»
733                     «IF property.returnType.name.contains("[")»
734                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
735                     «ELSE»
736                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
737                     «ENDIF»
738                 «ENDFOR»
739                 «IF augmentField != null»
740                     result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
741                 «ENDIF»
742                 return result;
743             }
744         «ENDIF»
745     '''
746
747     /**
748      * Template method which generates the method <code>equals()</code>.
749      *
750      * @return string with the <code>equals()</code> method definition in JAVA format
751      */
752     def protected generateEquals() '''
753         «IF !properties.empty || augmentField != null»
754             @Override
755             public boolean equals(«Object.importedName» obj) {
756                 if (this == obj) {
757                     return true;
758                 }
759                 if (!(obj instanceof «DataObject.importedName»)) {
760                     return false;
761                 }
762                 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
763                     return false;
764                 }
765                 «type.importedName» other = («type.importedName»)obj;
766                 «FOR property : properties»
767                     «val fieldName = property.fieldName»
768                     if («fieldName» == null) {
769                         if (other.«property.getterMethodName»() != null) {
770                             return false;
771                         }
772                     «IF property.returnType.name.contains("[")»
773                     } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
774                     «ELSE»
775                     } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
776                     «ENDIF»
777                         return false;
778                     }
779                 «ENDFOR»
780                 «IF augmentField != null»
781                     if (getClass() == obj.getClass()) {
782                         // Simple case: we are comparing against self
783                         «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
784                         «val fieldName = augmentField.name»
785                         if («fieldName» == null) {
786                             if (otherImpl.«fieldName» != null) {
787                                 return false;
788                             }
789                         } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
790                             return false;
791                         }
792                     } else {
793                         // Hard case: compare our augments with presence there...
794                         for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
795                             if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
796                                 return false;
797                             }
798                         }
799                         // .. and give the other one the chance to do the same
800                         if (!obj.equals(this)) {
801                             return false;
802                         }
803                     }
804                 «ENDIF»
805                 return true;
806             }
807         «ENDIF»
808     '''
809
810     def override generateToString(Collection<GeneratedProperty> properties) '''
811         «IF !(properties === null)»
812             @Override
813             public «String.importedName» toString() {
814                 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
815                 boolean first = true;
816
817                 «FOR property : properties»
818                     if («property.fieldName» != null) {
819                         if (first) {
820                             first = false;
821                         } else {
822                             builder.append(", ");
823                         }
824                         builder.append("«property.fieldName»=");
825                         «IF property.returnType.name.contains("[")»
826                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
827                         «ELSE»
828                             builder.append(«property.fieldName»);
829                         «ENDIF»
830                      }
831                 «ENDFOR»
832                 «IF augmentField != null»
833                     if (first) {
834                         first = false;
835                     } else {
836                         builder.append(", ");
837                     }
838                     builder.append("«augmentField.name»=");
839                     builder.append(«augmentField.name».values());
840                 «ENDIF»
841                 return builder.append(']').toString();
842             }
843         «ENDIF»
844     '''
845
846     override protected getFullyQualifiedName() {
847         '''«type.fullyQualifiedName»Builder'''.toString
848     }
849
850     def implementedInterfaceGetter() '''
851     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
852         return «type.importedName».class;
853     }
854     '''
855
856     private def createDescription(GeneratedType type) {
857         return '''
858         Class that builds {@link «type.importedName»} instances.
859
860         @see «type.importedName»
861     '''
862     }
863
864     override def protected String formatDataForJavaDoc(GeneratedType type) {
865         val typeDescription = createDescription(type)
866
867         return '''
868             «IF !typeDescription.nullOrEmpty»
869             «typeDescription»
870             «ENDIF»
871         '''.toString
872     }
873 }
874