BUG-1485: convert BuilderTemplate to use LengthGenerator
[mdsal.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.math.BigDecimal
13 import java.math.BigInteger
14 import java.util.ArrayList
15 import java.util.Arrays
16 import java.util.Collection
17 import java.util.Collections
18 import java.util.HashMap
19 import java.util.HashSet
20 import java.util.LinkedHashSet
21 import java.util.List
22 import java.util.Map
23 import java.util.Set
24 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
25 import org.opendaylight.yangtools.binding.generator.util.Types
26 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
27 import org.opendaylight.yangtools.concepts.Builder
28 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
29 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
30 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
31 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
32 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
33 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
34 import org.opendaylight.yangtools.sal.binding.model.api.Type
35 import org.opendaylight.yangtools.yang.binding.Augmentable
36 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
37 import org.opendaylight.yangtools.yang.binding.DataObject
38 import org.opendaylight.yangtools.yang.binding.Identifiable
39 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint
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 && !(restrictions.lengthConstraints.empty)»
423                     «LengthGenerator.generateLengthChecker(f.fieldName.toString, f.returnType, restrictions.lengthConstraints)»
424                 «ENDIF»
425             «ENDFOR»
426         «ENDIF»
427     '''
428
429     def private generateAugmentField(boolean isPrivate) '''
430         «IF augmentField != null»
431             «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
432         «ENDIF»
433     '''
434
435     /**
436      * Template method which generates setter methods
437      *
438      * @return string with the setter methods
439      */
440     def private generateSetters() '''
441         «FOR field : properties SEPARATOR '\n'»
442             «val restrictions = field.returnType.restrictions»
443             «IF restrictions != null»
444                 «IF !restrictions.rangeConstraints.nullOrEmpty»
445                     «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
446                     «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraints)»
447
448                 «ENDIF»
449             «ENDIF»
450             public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
451                 «IF restrictions != null && !restrictions.rangeConstraints.nullOrEmpty»
452                 if (value != null) {
453                     «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
454                     «IF field.returnType instanceof ConcreteType»
455                         «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
456                     «ELSE»
457                         «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
458                     «ENDIF»
459                 }
460                 «ENDIF»
461                 «generateRestrictions(field, "value")»
462                 this.«field.fieldName» = value;
463                 return this;
464             }
465             «generateLengthMethod(field.fieldName + "_length", field.returnType)»
466             «val range = field.fieldName + "_range"»
467             «generateRangeMethod(range, restrictions, field.returnType)»
468         «ENDFOR»
469         «IF augmentField != null»
470
471             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
472                 if (augmentation == null) {
473                     return remove«augmentField.name.toFirstUpper»(augmentationType);
474                 }
475
476                 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
477                     this.«augmentField.name» = new «HashMap.importedName»<>();
478                 }
479
480                 this.«augmentField.name».put(augmentationType, augmentation);
481                 return this;
482             }
483
484             public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
485                 if (this.«augmentField.name» instanceof «HashMap.importedName») {
486                     this.«augmentField.name».remove(augmentationType);
487                 }
488                 return this;
489             }
490         «ENDIF»
491     '''
492
493     def private generateRestrictions(GeneratedProperty field, String paramName) '''
494         «val Type type = field.returnType»
495         «val restrictions = type.getRestrictions»
496         «IF restrictions !== null && !restrictions.lengthConstraints.empty»
497             «IF type instanceof ConcreteType»
498                 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, paramName)»
499             «ELSE»
500                 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, paramName + ".getValue()")»
501             «ENDIF»
502         «ENDIF»
503     '''
504
505     @Deprecated
506     def private generateLengthMethod(String methodName, Type type) '''
507         «val Restrictions restrictions = type.restrictions»
508         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
509             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
510             /**
511              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
512              */
513             @Deprecated
514             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
515                 «IF numberClass.equals(typeof(BigDecimal))»
516                     «lengthBody(restrictions, numberClass)»
517                 «ELSE»
518                     «lengthBody(restrictions, typeof(BigInteger))»
519                 «ENDIF»
520             }
521         «ENDIF»
522     '''
523
524     @Deprecated
525     def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass) '''
526         «List.importedName»<«Range.importedName»<«numberClass.importedName»>> ret = new «ArrayList.importedName»<>(«restrictions.lengthConstraints.size»);
527         «FOR r : restrictions.lengthConstraints»
528             ret.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
529         «ENDFOR»
530         return ret;
531     '''
532
533     @Deprecated
534     def private generateRangeMethod(String methodName, Restrictions restrictions, Type returnType) '''
535         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
536             «val number = returnType.importedNumber»
537             /**
538              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
539              */
540             @Deprecated
541             public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
542                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
543                     «rangeBody(restrictions.rangeConstraints, BigDecimal)»
544                 «ELSE»
545                     «rangeBody(restrictions.rangeConstraints, BigInteger)»
546                 «ENDIF»
547             }
548         «ENDIF»
549     '''
550
551     @Deprecated
552     def private rangeBody(List<RangeConstraint> restrictions, Class<? extends Number> numberClass) '''
553          final «List.importedName»<«Range.importedName»<«numberClass.importedName»>> ret = new java.util.ArrayList<>(«restrictions.size»);
554          «FOR r : restrictions»
555              ret.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
556          «ENDFOR»
557          return ret;
558     '''
559
560     def private CharSequence generateCopyConstructor(boolean impl) '''
561         «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
562             «val allProps = new ArrayList(properties)»
563             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
564             «val keyType = type.getKey»
565             «IF isList && keyType != null»
566                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
567                 «Collections.sort(keyProps,
568                     [ p1, p2 |
569                         return p1.name.compareTo(p2.name)
570                     ])
571                 »
572                 «FOR field : keyProps»
573                     «removeProperty(allProps, field.name)»
574                 «ENDFOR»
575                 «removeProperty(allProps, "key")»
576                 if (base.getKey() == null) {
577                     this._key = new «keyType.importedName»(
578                         «FOR keyProp : keyProps SEPARATOR ", "»
579                             base.«keyProp.getterMethodName»()
580                         «ENDFOR»
581                     );
582                     «FOR field : keyProps»
583                         this.«field.fieldName» = base.«field.getterMethodName»();
584                     «ENDFOR»
585                 } else {
586                     this._key = base.getKey();
587                     «FOR field : keyProps»
588                            this.«field.fieldName» = _key.«field.getterMethodName»();
589                     «ENDFOR»
590                 }
591             «ENDIF»
592             «FOR field : allProps»
593                 this.«field.fieldName» = base.«field.getterMethodName»();
594             «ENDFOR»
595             «IF augmentField != null»
596                 «IF impl»
597                     switch (base.«augmentField.name».size()) {
598                     case 0:
599                         this.«augmentField.name» = «Collections.importedName».emptyMap();
600                         break;
601                     case 1:
602                         final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
603                         this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
604                         break;
605                     default :
606                         this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
607                     }
608                 «ELSE»
609                     if (base instanceof «type.name»«IMPL») {
610                         «type.name»«IMPL» impl = («type.name»«IMPL») base;
611                         if (!impl.«augmentField.name».isEmpty()) {
612                             this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
613                         }
614                     } else if (base instanceof «AugmentationHolder.importedName») {
615                         @SuppressWarnings("unchecked")
616                         «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
617                         if (!casted.augmentations().isEmpty()) {
618                             this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
619                         }
620                     }
621                 «ENDIF»
622             «ENDIF»
623         }
624     '''
625
626     private def boolean implementsIfc(GeneratedType type, Type impl) {
627         for (Type ifc : type.implements) {
628             if (ifc.equals(impl)) {
629                 return true;
630             }
631         }
632         return false;
633     }
634
635     private def Type getKey(GeneratedType type) {
636         for (m : type.methodDefinitions) {
637             if ("getKey".equals(m.name)) {
638                 return m.returnType;
639             }
640         }
641         return null;
642     }
643
644     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
645         var GeneratedProperty toRemove = null
646         for (p : props) {
647             if (p.name.equals(name)) {
648                 toRemove = p;
649             }
650         }
651         if (toRemove != null) {
652             props.remove(toRemove);
653         }
654     }
655
656     /**
657      * Template method which generate getter methods for IMPL class.
658      *
659      * @return string with getter methods
660      */
661     def private generateGetters(boolean addOverride) '''
662         «IF !properties.empty»
663             «FOR field : properties SEPARATOR '\n'»
664                 «IF addOverride»@Override«ENDIF»
665                 «field.getterMethod»
666             «ENDFOR»
667         «ENDIF»
668         «IF augmentField != null»
669
670             @SuppressWarnings("unchecked")
671             «IF addOverride»@Override«ENDIF»
672             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
673                 if (augmentationType == null) {
674                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
675                 }
676                 return (E) «augmentField.name».get(augmentationType);
677             }
678         «ENDIF»
679     '''
680
681     /**
682      * Template method which generates the method <code>hashCode()</code>.
683      *
684      * @return string with the <code>hashCode()</code> method definition in JAVA format
685      */
686     def protected generateHashCode() '''
687         «IF !properties.empty || augmentField != null»
688             @Override
689             public int hashCode() {
690                 final int prime = 31;
691                 int result = 1;
692                 «FOR property : properties»
693                     «IF property.returnType.name.contains("[")»
694                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
695                     «ELSE»
696                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
697                     «ENDIF»
698                 «ENDFOR»
699                 «IF augmentField != null»
700                     result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
701                 «ENDIF»
702                 return result;
703             }
704         «ENDIF»
705     '''
706
707     /**
708      * Template method which generates the method <code>equals()</code>.
709      *
710      * @return string with the <code>equals()</code> method definition in JAVA format
711      */
712     def protected generateEquals() '''
713         «IF !properties.empty || augmentField != null»
714             @Override
715             public boolean equals(«Object.importedName» obj) {
716                 if (this == obj) {
717                     return true;
718                 }
719                 if (!(obj instanceof «DataObject.importedName»)) {
720                     return false;
721                 }
722                 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
723                     return false;
724                 }
725                 «type.importedName» other = («type.importedName»)obj;
726                 «FOR property : properties»
727                     «val fieldName = property.fieldName»
728                     if («fieldName» == null) {
729                         if (other.«property.getterMethodName»() != null) {
730                             return false;
731                         }
732                     «IF property.returnType.name.contains("[")»
733                     } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
734                     «ELSE»
735                     } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
736                     «ENDIF»
737                         return false;
738                     }
739                 «ENDFOR»
740                 «IF augmentField != null»
741                     if (getClass() == obj.getClass()) {
742                         // Simple case: we are comparing against self
743                         «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
744                         «val fieldName = augmentField.name»
745                         if («fieldName» == null) {
746                             if (otherImpl.«fieldName» != null) {
747                                 return false;
748                             }
749                         } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
750                             return false;
751                         }
752                     } else {
753                         // Hard case: compare our augments with presence there...
754                         for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
755                             if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
756                                 return false;
757                             }
758                         }
759                         // .. and give the other one the chance to do the same
760                         if (!obj.equals(this)) {
761                             return false;
762                         }
763                     }
764                 «ENDIF»
765                 return true;
766             }
767         «ENDIF»
768     '''
769
770     def override generateToString(Collection<GeneratedProperty> properties) '''
771         «IF !(properties === null)»
772             @Override
773             public «String.importedName» toString() {
774                 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
775                 boolean first = true;
776
777                 «FOR property : properties»
778                     if («property.fieldName» != null) {
779                         if (first) {
780                             first = false;
781                         } else {
782                             builder.append(", ");
783                         }
784                         builder.append("«property.fieldName»=");
785                         «IF property.returnType.name.contains("[")»
786                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
787                         «ELSE»
788                             builder.append(«property.fieldName»);
789                         «ENDIF»
790                      }
791                 «ENDFOR»
792                 «IF augmentField != null»
793                     if (first) {
794                         first = false;
795                     } else {
796                         builder.append(", ");
797                     }
798                     builder.append("«augmentField.name»=");
799                     builder.append(«augmentField.name».values());
800                 «ENDIF»
801                 return builder.append(']').toString();
802             }
803         «ENDIF»
804     '''
805
806     def implementedInterfaceGetter() '''
807     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
808         return «type.importedName».class;
809     }
810     '''
811
812     private def createDescription(GeneratedType type) {
813         return '''
814         Class that builds {@link «type.importedName»} instances.
815
816         @see «type.importedName»
817     '''
818     }
819
820     override def protected String formatDataForJavaDoc(GeneratedType type) {
821         val typeDescription = createDescription(type)
822
823         return '''
824             «IF !typeDescription.nullOrEmpty»
825             «typeDescription»
826             «ENDIF»
827         '''.toString
828     }
829 }
830