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