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