Make wrapped class equals() final
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / ClassTemplate.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 java.util.Objects.requireNonNull
11 import static org.opendaylight.mdsal.binding.model.util.Types.BOOLEAN;
12 import static org.opendaylight.mdsal.binding.model.util.Types.BYTE_ARRAY;
13 import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
14 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
15
16 import com.google.common.base.Preconditions
17 import com.google.common.collect.ImmutableList
18 import com.google.common.collect.Lists
19 import java.beans.ConstructorProperties
20 import java.util.ArrayList
21 import java.util.Base64;
22 import java.util.Collections
23 import java.util.List
24 import java.util.Map
25 import java.util.regex.Pattern
26 import org.gaul.modernizer_maven_annotations.SuppressModernizer
27 import org.opendaylight.mdsal.binding.model.api.ConcreteType
28 import org.opendaylight.mdsal.binding.model.api.Constant
29 import org.opendaylight.mdsal.binding.model.api.Enumeration
30 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
31 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
32 import org.opendaylight.mdsal.binding.model.api.Restrictions
33 import org.opendaylight.mdsal.binding.model.api.Type
34 import org.opendaylight.mdsal.binding.model.util.TypeConstants
35 import org.opendaylight.yangtools.yang.binding.CodeHelpers
36 import org.opendaylight.yangtools.yang.common.Empty
37 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
38
39 /**
40  * Template for generating JAVA class.
41  */
42 @SuppressModernizer
43 class ClassTemplate extends BaseTemplate {
44
45     protected val List<GeneratedProperty> properties
46     protected val List<GeneratedProperty> finalProperties
47     protected val List<GeneratedProperty> parentProperties
48     protected val Iterable<GeneratedProperty> allProperties
49     protected val Restrictions restrictions
50
51     /**
52      * List of enumeration which are generated as JAVA enum type.
53      */
54     protected val List<Enumeration> enums
55
56     /**
57      * List of constant instances which are generated as JAVA public static final attributes.
58      */
59     protected val List<Constant> consts
60
61     protected val GeneratedTransferObject genTO
62
63     val AbstractRangeGenerator<?> rangeGenerator
64
65     /**
66      * Creates instance of this class with concrete <code>genType</code>.
67      *
68      * @param genType generated transfer object which will be transformed to JAVA class source code
69      */
70     new(GeneratedTransferObject genType) {
71         this(new TopLevelJavaGeneratedType(genType), genType)
72     }
73
74     /**
75      * Creates instance of this class with concrete <code>genType</code>.
76      *
77      * @param genType generated transfer object which will be transformed to JAVA class source code
78      */
79     new(AbstractJavaGeneratedType javaType, GeneratedTransferObject genType) {
80         super(javaType, genType)
81         this.genTO = genType
82         this.properties = genType.properties
83         this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
84         this.parentProperties = GeneratorUtil.getPropertiesOfAllParents(genTO)
85         this.restrictions = genType.restrictions
86
87         var List<GeneratedProperty> sorted = new ArrayList<GeneratedProperty>();
88         sorted.addAll(properties);
89         sorted.addAll(parentProperties);
90         Collections.sort(sorted, [p1, p2|
91             p1.name.compareTo(p2.name)
92         ]);
93
94         this.allProperties = sorted
95         this.enums = genType.enumerations
96         this.consts = genType.constantDefinitions
97
98         if (restrictions !== null && restrictions.rangeConstraint.present) {
99             rangeGenerator = requireNonNull(AbstractRangeGenerator.forType(findProperty(genType, "value").returnType))
100         } else {
101             rangeGenerator = null
102         }
103     }
104
105     /**
106      * Generates JAVA class source code (class body only).
107      *
108      * @return string with JAVA class body source code
109      */
110     def CharSequence generateAsInnerClass() {
111         return generateBody(true)
112     }
113
114     override protected body() {
115         generateBody(false);
116     }
117
118     /**
119      * Template method which generates class body.
120      *
121      * @param isInnerClass boolean value which specify if generated class is|isn't inner
122      * @return string with class source code in JAVA format
123      */
124     def protected generateBody(boolean isInnerClass) '''
125         «wrapToDocumentation(formatDataForJavaDoc(type))»
126         «annotationDeclaration»
127         «generateClassDeclaration(isInnerClass)» {
128             «suidDeclaration»
129             «innerClassesDeclarations»
130             «enumDeclarations»
131             «constantsDeclarations»
132             «generateFields»
133
134             «IF restrictions !== null»
135                 «IF restrictions.lengthConstraint.present»
136                     «LengthGenerator.generateLengthChecker("_value", findProperty(genTO, "value").returnType, restrictions.lengthConstraint.get, this)»
137                 «ENDIF»
138                 «IF restrictions.rangeConstraint.present»
139                     «rangeGenerator.generateRangeChecker("_value", restrictions.rangeConstraint.get, this)»
140                 «ENDIF»
141             «ENDIF»
142
143             «constructors»
144
145             «defaultInstance»
146
147             «FOR field : properties SEPARATOR "\n"»
148                 «field.getterMethod»
149                 «IF !field.readOnly»
150                     «field.setterMethod»
151                 «ENDIF»
152             «ENDFOR»
153
154             «IF (genTO.isTypedef() && genTO.getBaseType instanceof BitsTypeDefinition)»
155                 «generateGetValueForBitsTypeDef»
156             «ENDIF»
157
158             «generateHashCode»
159
160             «generateEquals»
161
162             «generateToString(genTO.toStringIdentifiers)»
163         }
164
165     '''
166
167     /**
168      * Template method which generates the method <code>getValue()</code> for typedef,
169      * which base type is BitsDefinition.
170      *
171      * @return string with the <code>getValue()</code> method definition in JAVA format
172      */
173     def protected generateGetValueForBitsTypeDef() '''
174
175         public boolean[] getValue() {
176             return new boolean[]{
177             «FOR property: genTO.properties SEPARATOR ','»
178                  «property.fieldName»
179             «ENDFOR»
180             };
181         }
182     '''
183
184     /**
185      * Template method which generates inner classes inside this interface.
186      *
187      * @return string with the source code for inner classes in JAVA format
188      */
189     def protected innerClassesDeclarations() '''
190         «IF !type.enclosedTypes.empty»
191             «FOR innerClass : type.enclosedTypes SEPARATOR "\n"»
192                 «generateInnerClass(innerClass)»
193             «ENDFOR»
194         «ENDIF»
195     '''
196
197     def protected constructors() '''
198         «IF genTO.unionType»
199             «genUnionConstructor»
200         «ELSE»
201             «allValuesConstructor»
202         «ENDIF»
203         «IF !allProperties.empty»
204             «copyConstructor»
205         «ENDIF»
206         «IF properties.empty && !parentProperties.empty »
207             «parentConstructor»
208         «ENDIF»
209     '''
210
211     def protected allValuesConstructor() '''
212     «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
213         @«ConstructorProperties.importedName»("value")
214     «ENDIF»
215     public «type.name»(«allProperties.asArgumentsDeclaration») {
216         «IF false == parentProperties.empty»
217             super(«parentProperties.asArguments»);
218         «ENDIF»
219         «FOR p : allProperties»
220             «generateRestrictions(type, p.fieldName.toString, p.returnType)»
221         «ENDFOR»
222
223         «/*
224          * If we have patterns, we need to apply them to the value field. This is a sad
225          * consequence of how this code is structured.
226          */
227         IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
228             «CodeHelpers.importedName».requireValue(_value);
229             «genPatternEnforcer("_value")»
230         «ENDIF»
231
232         «FOR p : properties»
233             «IF p.returnType.importedName.contains("[]")»
234                 «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name
235                 .equals("value")»
236                 this.«p.fieldName» = «p.fieldName».clone();
237                 «ELSE»
238                 this.«p.fieldName» = «p.fieldName» == null ? null : «p.fieldName».clone();
239                 «ENDIF»
240             «ELSE»
241             this.«p.fieldName» = «p.fieldName»;
242             «ENDIF»
243         «ENDFOR»
244     }
245
246     '''
247
248     def protected genUnionConstructor() '''
249     «FOR p : allProperties»
250         «val List<GeneratedProperty> other = new ArrayList(properties)»
251         «IF other.remove(p)»
252             «genConstructor(p, other)»
253         «ENDIF»
254     «ENDFOR»
255
256     '''
257
258     def protected genConstructor(GeneratedProperty property, GeneratedProperty... other) '''
259     public «type.name»(«property.returnType.importedName + " " + property.name») {
260         «IF false == parentProperties.empty»
261             super(«parentProperties.asArguments»);
262         «ENDIF»
263
264         «generateRestrictions(type, property.fieldName.toString, property.returnType)»
265
266         this.«property.fieldName» = «property.name»;
267         «FOR p : other»
268             this.«p.fieldName» = null;
269         «ENDFOR»
270     }
271     '''
272
273     def private genPatternEnforcer(String ref) '''
274         «FOR c : consts»
275             «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
276             «CodeHelpers.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
277             «ENDIF»
278         «ENDFOR»
279     '''
280
281     def private static paramValue(Type returnType, String paramName) {
282         if (returnType instanceof ConcreteType) {
283             return paramName
284         } else {
285             return paramName + ".getValue()"
286         }
287     }
288
289     def private generateRestrictions(Type type, String paramName, Type returnType) '''
290         «val restrictions = type.restrictions»
291         «IF restrictions !== null»
292             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
293             if («paramName» != null) {
294                 «IF restrictions.lengthConstraint.present»
295                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
296                 «ENDIF»
297                 «IF restrictions.rangeConstraint.present»
298                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
299                 «ENDIF»
300             }
301             «ENDIF»
302         «ENDIF»
303     '''
304
305     def protected copyConstructor() '''
306     /**
307      * Creates a copy from Source Object.
308      *
309      * @param source Source object
310      */
311     public «type.name»(«type.name» source) {
312         «IF false == parentProperties.empty»
313             super(source);
314         «ENDIF»
315         «FOR p : properties»
316             this.«p.fieldName» = source.«p.fieldName»;
317         «ENDFOR»
318     }
319     '''
320
321     def protected parentConstructor() '''
322     /**
323      * Creates a new instance from «genTO.superType.importedName»
324      *
325      * @param source Source object
326      */
327     public «type.name»(«genTO.superType.importedName» source) {
328         super(source);
329         «genPatternEnforcer("getValue()")»
330     }
331     '''
332
333     def protected defaultInstance() '''
334         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
335             «val prop = allProperties.get(0)»
336             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
337             public static «genTO.name» getDefaultInstance(String defaultValue) {
338                 «IF BYTE_ARRAY.equals(prop.returnType)»
339                     return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
340                 «ELSEIF STRING.equals(prop.returnType)»
341                     return new «genTO.name»(defaultValue);
342                 «ELSEIF Constants.EMPTY.equals(prop.returnType)»
343                     «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
344                     return new «genTO.name»(«Empty.importedName».getInstance());
345                 «ELSEIF allProperties.size > 1»
346                     «bitsArgs»
347                 «ELSEIF BOOLEAN.equals(prop.returnType)»
348                     return new «genTO.name»(«Boolean.importedName».valueOf(defaultValue));
349                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
350                     return new «genTO.name»(«Byte.importedName».valueOf(defaultValue));
351                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
352                     return new «genTO.name»(«Short.importedName».valueOf(defaultValue));
353                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
354                     return new «genTO.name»(«Integer.importedName».valueOf(defaultValue));
355                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
356                     return new «genTO.name»(«Long.importedName».valueOf(defaultValue));
357                 «ELSE»
358                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
359                 «ENDIF»
360             }
361             «ENDIF»
362         «ENDIF»
363     '''
364
365     def protected bitsArgs() '''
366         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
367         if (!properties.contains(defaultValue)) {
368             throw new «IllegalArgumentException.importedName»("invalid default parameter");
369         }
370         int i = 0;
371         return new «genTO.name»(
372         «FOR prop : allProperties SEPARATOR ","»
373             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
374         «ENDFOR»
375         );
376     '''
377
378     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
379         «FOR prop : properties SEPARATOR ","»
380             "«prop.name»"
381         «ENDFOR»
382     '''
383
384     /**
385      * Template method which generates JAVA class declaration.
386      *
387      * @param isInnerClass boolean value which specify if generated class is|isn't inner
388      * @return string with class declaration in JAVA format
389      */
390     def protected generateClassDeclaration(boolean isInnerClass) '''
391         public«
392         IF (isInnerClass)»«
393             " static final "»«
394         ELSEIF (type.abstract)»«
395             " abstract "»«
396         ELSE»«
397             " "»«
398         ENDIF»class «type.name»«
399         IF (genTO.superType !== null)»«
400             " extends "»«genTO.superType.importedName»«
401         ENDIF»
402         «IF (!type.implements.empty)»«
403             " implements "»«
404             FOR type : type.implements SEPARATOR ", "»«
405                 type.importedName»«
406             ENDFOR»«
407         ENDIF
408     »'''
409
410     /**
411      * Template method which generates JAVA enum type.
412      *
413      * @return string with inner enum source code in JAVA format
414      */
415     def protected enumDeclarations() '''
416         «IF !enums.empty»
417             «FOR e : enums SEPARATOR "\n"»
418                 «new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
419             «ENDFOR»
420         «ENDIF»
421     '''
422
423     def protected suidDeclaration() '''
424         «IF genTO.SUID !== null»
425             private static final long serialVersionUID = «genTO.SUID.value»L;
426         «ENDIF»
427     '''
428
429     def protected annotationDeclaration() '''
430         «IF genTO.getAnnotations !== null»
431             «FOR e : genTO.getAnnotations»
432                 @«e.getName»
433             «ENDFOR»
434         «ENDIF»
435     '''
436
437     /**
438      * Template method which generates JAVA constants.
439      *
440      * @return string with constants in JAVA format
441      */
442     def protected constantsDeclarations() '''
443         «IF !consts.empty»
444             «FOR c : consts»
445                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
446                     «val cValue = c.value as Map<String, String>»
447                     public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
448                     FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
449                     «IF cValue.size == 1»
450                         private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST» = «Pattern.importedName».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
451                         private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.get(0).escapeJava»";
452                     «ELSE»
453                         private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST» = «CodeHelpers.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
454                         private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
455                         FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
456                     «ENDIF»
457                 «ELSE»
458                     «emitConstant(c)»
459                 «ENDIF»
460             «ENDFOR»
461         «ENDIF»
462     '''
463
464     /**
465      * Template method which generates JAVA class attributes.
466      *
467      * @return string with the class attributes in JAVA format
468      */
469     def protected generateFields() '''
470         «IF !properties.empty»
471             «FOR f : properties»
472                 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
473             «ENDFOR»
474         «ENDIF»
475     '''
476
477     protected def isReadOnly(GeneratedProperty field) {
478         return field.readOnly
479     }
480
481     /**
482      * Template method which generates the method <code>hashCode()</code>.
483      *
484      * @return string with the <code>hashCode()</code> method definition in JAVA format
485      */
486     def protected generateHashCode() {
487         val size = genTO.hashCodeIdentifiers.size
488         if (size == 0) {
489             return ""
490         }
491         return '''
492             @«Override.importedName»
493             public int hashCode() {
494                 «IF size != 1»
495                     «hashCodeResult(genTO.hashCodeIdentifiers)»
496                     return result;
497                 «ELSE»
498                     return «CodeHelpers.importedName».wrapperHashCode(«genTO.hashCodeIdentifiers.get(0).fieldName»);
499                 «ENDIF»
500             }
501         '''
502     }
503
504     /**
505      * Template method which generates the method <code>equals()</code>.
506      *
507      * @return string with the <code>equals()</code> method definition in JAVA format
508      */
509     def private generateEquals() '''
510         «IF !genTO.equalsIdentifiers.empty»
511             @«Override.importedName»
512             public final boolean equals(java.lang.Object obj) {
513                 if (this == obj) {
514                     return true;
515                 }
516                 if (!(obj instanceof «type.name»)) {
517                     return false;
518                 }
519                 final «type.name» other = («type.name») obj;
520                 «FOR property : genTO.equalsIdentifiers»
521                     «val fieldName = property.fieldName»
522                     if (!«property.importedUtilClass».equals(«fieldName», other.«fieldName»)) {
523                         return false;
524                     }
525                 «ENDFOR»
526                 return true;
527             }
528         «ENDIF»
529     '''
530
531     def GeneratedProperty getPropByName(String name) {
532         for (GeneratedProperty prop : allProperties) {
533             if (prop.name.equals(name)) {
534                 return prop;
535             }
536         }
537         return null;
538     }
539
540 }