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