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