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