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