Bug 3147 - Binding spec v1: auto generated code by YANGTOOLS could
[mdsal.git] / binding / mdsal-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 java.util.ArrayList
12 import java.util.Arrays
13 import java.util.Collection
14 import java.util.Collections
15 import java.util.HashMap
16 import java.util.HashSet
17 import java.util.LinkedHashSet
18 import java.util.List
19 import java.util.Map
20 import java.util.Objects
21 import java.util.Set
22 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
23 import org.opendaylight.yangtools.binding.generator.util.Types
24 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
25 import org.opendaylight.yangtools.concepts.Builder
26 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
28 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
29 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
30 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
31 import org.opendaylight.yangtools.sal.binding.model.api.Type
32 import org.opendaylight.yangtools.yang.binding.Augmentable
33 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
34 import org.opendaylight.yangtools.yang.binding.DataObject
35 import org.opendaylight.yangtools.yang.binding.Identifiable
36
37 /**
38  * Template for generating JAVA builder classes.
39  */
40
41 class BuilderTemplate extends BaseTemplate {
42
43     /**
44      * Constant with the name of the concrete method.
45      */
46     val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
47
48     /**
49      * Constant with the suffix for builder classes.
50      */
51     val static BUILDER = 'Builder'
52
53     /**
54      * Constant with the name of the BuilderFor interface
55      */
56      val static BUILDERFOR = Builder.simpleName;
57
58     /**
59      * Constant with suffix for the classes which are generated from the builder classes.
60      */
61     val static IMPL = 'Impl'
62
63     /**
64      * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
65      */
66     var GeneratedProperty augmentField
67
68     /**
69      * Set of class attributes (fields) which are derived from the getter methods names
70      */
71     val Set<GeneratedProperty> properties
72
73     private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
74     private static val APPEND_COMMA = "builder.append(\", \");";
75     private static val APPEND_COMMA_WITH_WS = "\n    "+ APPEND_COMMA + "\n}";
76     private static val APPEND_ENDIF = "    }\n";
77     private static val APPEND_END = "return builder.append(']').toString();";
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             «ENDFOR»
422         «ENDIF»
423     '''
424
425     def private generateAugmentField(boolean isPrivate) '''
426         «IF augmentField != null»
427             «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
428         «ENDIF»
429     '''
430
431     /**
432      * Template method which generates setter methods
433      *
434      * @return string with the setter methods
435      */
436     def private generateSetters() '''
437         «FOR field : properties SEPARATOR '\n'»
438              «/* FIXME: generate checkers as simple blocks and embed them directly in setters  */»
439              «val restrictions = field.returnType.restrictions»
440              «IF !(field.returnType instanceof GeneratedType) && restrictions != null»
441                     «IF !restrictions.rangeConstraints.nullOrEmpty»
442                         «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
443                         «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraints)»
444
445                     «ENDIF»
446                     «IF !restrictions.lengthConstraints.nullOrEmpty»
447                     «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraints)»
448
449                     «ENDIF»
450             «ENDIF»
451             public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
452             «IF !(field.returnType instanceof GeneratedType) && restrictions != null»
453                 «IF restrictions != null && (!restrictions.rangeConstraints.nullOrEmpty || !restrictions.lengthConstraints.nullOrEmpty)»
454                 if (value != null) {
455                     «IF !restrictions.rangeConstraints.nullOrEmpty»
456                         «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
457                         «IF field.returnType instanceof ConcreteType»
458                             «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
459                         «ELSE»
460                             «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
461                         «ENDIF»
462                     «ENDIF»
463                     «IF !restrictions.lengthConstraints.nullOrEmpty»
464                         «IF field.returnType instanceof ConcreteType»
465                             «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
466                          «ELSE»
467                             «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
468                         «ENDIF»
469                     «ENDIF»
470                 }
471                 «ENDIF»
472             «ENDIF»
473                 this.«field.fieldName» = value;
474                 return this;
475             }
476         «ENDFOR»
477         «IF augmentField != null»
478
479             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
480                 if (augmentation == null) {
481                     return remove«augmentField.name.toFirstUpper»(augmentationType);
482                 }
483
484                 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
485                     this.«augmentField.name» = new «HashMap.importedName»<>();
486                 }
487
488                 this.«augmentField.name».put(augmentationType, augmentation);
489                 return this;
490             }
491
492             public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
493                 if (this.«augmentField.name» instanceof «HashMap.importedName») {
494                     this.«augmentField.name».remove(augmentationType);
495                 }
496                 return this;
497             }
498         «ENDIF»
499     '''
500
501     def private CharSequence generateCopyConstructor(boolean impl) '''
502         «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
503             «val allProps = new ArrayList(properties)»
504             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
505             «val keyType = type.getKey»
506             «IF isList && keyType != null»
507                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
508                 «Collections.sort(keyProps,
509                     [ p1, p2 |
510                         return p1.name.compareTo(p2.name)
511                     ])
512                 »
513                 «FOR field : keyProps»
514                     «removeProperty(allProps, field.name)»
515                 «ENDFOR»
516                 «removeProperty(allProps, "key")»
517                 if (base.getKey() == null) {
518                     this._key = new «keyType.importedName»(
519                         «FOR keyProp : keyProps SEPARATOR ", "»
520                             base.«keyProp.getterMethodName»()
521                         «ENDFOR»
522                     );
523                     «FOR field : keyProps»
524                         this.«field.fieldName» = base.«field.getterMethodName»();
525                     «ENDFOR»
526                 } else {
527                     this._key = base.getKey();
528                     «FOR field : keyProps»
529                            this.«field.fieldName» = _key.«field.getterMethodName»();
530                     «ENDFOR»
531                 }
532             «ENDIF»
533             «FOR field : allProps»
534                 this.«field.fieldName» = base.«field.getterMethodName»();
535             «ENDFOR»
536             «IF augmentField != null»
537                 «IF impl»
538                     switch (base.«augmentField.name».size()) {
539                     case 0:
540                         this.«augmentField.name» = «Collections.importedName».emptyMap();
541                         break;
542                     case 1:
543                         final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
544                         this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
545                         break;
546                     default :
547                         this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
548                     }
549                 «ELSE»
550                     if (base instanceof «type.name»«IMPL») {
551                         «type.name»«IMPL» impl = («type.name»«IMPL») base;
552                         if (!impl.«augmentField.name».isEmpty()) {
553                             this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
554                         }
555                     } else if (base instanceof «AugmentationHolder.importedName») {
556                         @SuppressWarnings("unchecked")
557                         «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
558                         if (!casted.augmentations().isEmpty()) {
559                             this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
560                         }
561                     }
562                 «ENDIF»
563             «ENDIF»
564         }
565     '''
566
567     private def boolean implementsIfc(GeneratedType type, Type impl) {
568         for (Type ifc : type.implements) {
569             if (ifc.equals(impl)) {
570                 return true;
571             }
572         }
573         return false;
574     }
575
576     private def Type getKey(GeneratedType type) {
577         for (m : type.methodDefinitions) {
578             if ("getKey".equals(m.name)) {
579                 return m.returnType;
580             }
581         }
582         return null;
583     }
584
585     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
586         var GeneratedProperty toRemove = null
587         for (p : props) {
588             if (p.name.equals(name)) {
589                 toRemove = p;
590             }
591         }
592         if (toRemove != null) {
593             props.remove(toRemove);
594         }
595     }
596
597     /**
598      * Template method which generate getter methods for IMPL class.
599      *
600      * @return string with getter methods
601      */
602     def private generateGetters(boolean addOverride) '''
603         «IF !properties.empty»
604             «FOR field : properties SEPARATOR '\n'»
605                 «IF addOverride»@Override«ENDIF»
606                 «field.getterMethod»
607             «ENDFOR»
608         «ENDIF»
609         «IF augmentField != null»
610
611             @SuppressWarnings("unchecked")
612             «IF addOverride»@Override«ENDIF»
613             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
614                 if (augmentationType == null) {
615                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
616                 }
617                 return (E) «augmentField.name».get(augmentationType);
618             }
619         «ENDIF»
620     '''
621
622     /**
623      * Template method which generates the method <code>hashCode()</code>.
624      *
625      * @return string with the <code>hashCode()</code> method definition in JAVA format
626      */
627     def protected generateHashCode() '''
628         «IF !properties.empty || augmentField != null»
629             private int hash = 0;
630             private volatile boolean hashValid = false;
631
632             @Override
633             public int hashCode() {
634                 if (hashValid) {
635                     return hash;
636                 }
637
638                 final int prime = 31;
639                 int result = 1;
640                 «FOR property : properties»
641                     «IF property.returnType.name.contains("[")»
642                     result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
643                     «ELSE»
644                     result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
645                     «ENDIF»
646                 «ENDFOR»
647                 «IF augmentField != null»
648                     result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
649                 «ENDIF»
650
651                 hash = result;
652                 hashValid = true;
653                 return result;
654             }
655         «ENDIF»
656     '''
657
658     /**
659      * Template method which generates the method <code>equals()</code>.
660      *
661      * @return string with the <code>equals()</code> method definition in JAVA format
662      */
663     def protected generateEquals() '''
664         «IF !properties.empty || augmentField != null»
665             @Override
666             public boolean equals(«Object.importedName» obj) {
667                 if (this == obj) {
668                     return true;
669                 }
670                 if (!(obj instanceof «DataObject.importedName»)) {
671                     return false;
672                 }
673                 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
674                     return false;
675                 }
676                 «type.importedName» other = («type.importedName»)obj;
677                 «FOR property : properties»
678                     «val fieldName = property.fieldName»
679                     «IF property.returnType.name.contains("[")»
680                     if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
681                     «ELSE»
682                     if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
683                     «ENDIF»
684                         return false;
685                     }
686                 «ENDFOR»
687                 «IF augmentField != null»
688                     if (getClass() == obj.getClass()) {
689                         // Simple case: we are comparing against self
690                         «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
691                         «val fieldName = augmentField.name»
692                         if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
693                             return false;
694                         }
695                     } else {
696                         // Hard case: compare our augments with presence there...
697                         for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
698                             if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
699                                 return false;
700                             }
701                         }
702                         // .. and give the other one the chance to do the same
703                         if (!obj.equals(this)) {
704                             return false;
705                         }
706                     }
707                 «ENDIF»
708                 return true;
709             }
710         «ENDIF»
711     '''
712
713     def override generateToString(Collection<GeneratedProperty> properties) '''
714         «IF !(properties === null)»
715             @Override
716             public «String.importedName» toString() {
717                 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
718                 «FOR property : properties SEPARATOR APPEND_COMMA_WITH_WS AFTER APPEND_ENDIF»
719                     if («property.fieldName» != null) {
720                         builder.append("«property.fieldName»=");
721                         «IF property.returnType.name.contains("[")»
722                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
723                         «ELSE»
724                             builder.append(«property.fieldName»);
725                         «ENDIF»
726                 «ENDFOR»
727                 «IF augmentField != null»
728                     «IF !properties.empty»
729                 «APPEND_COMMA»
730                     «ENDIF»
731                     builder.append("«augmentField.name»=");
732                     builder.append(«augmentField.name».values());«"\n"»
733                     «APPEND_END»
734                 «ELSE»
735                     «IF properties.empty»
736                     «APPEND_END»
737                     «ELSE»
738             «APPEND_END»
739                     «ENDIF»
740                 «ENDIF»
741             }
742         «ENDIF»
743     '''
744
745     def implementedInterfaceGetter() '''
746     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
747         return «type.importedName».class;
748     }
749     '''
750
751     private def createDescription(GeneratedType type) {
752         return '''
753         Class that builds {@link «type.importedName»} instances.
754
755         @see «type.importedName»
756     '''
757     }
758
759     override def protected String formatDataForJavaDoc(GeneratedType type) {
760         val typeDescription = createDescription(type)
761
762         return '''
763             «IF !typeDescription.nullOrEmpty»
764             «typeDescription»
765             «ENDIF»
766         '''.toString
767     }
768 }
769