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