Optimize array checks
[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.Comparator
23 import java.util.List
24 import java.util.Map
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.common.Empty
36 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
37
38 /**
39  * Template for generating JAVA class.
40  */
41 class ClassTemplate extends BaseTemplate {
42     static val Comparator<GeneratedProperty> PROP_COMPARATOR = Comparator.comparing([prop | prop.name])
43
44     protected val List<GeneratedProperty> properties
45     protected val List<GeneratedProperty> finalProperties
46     protected val List<GeneratedProperty> parentProperties
47     protected val List<GeneratedProperty> allProperties
48     protected val Restrictions restrictions
49
50     /**
51      * List of enumeration which are generated as JAVA enum type.
52      */
53     protected val List<Enumeration> enums
54
55     /**
56      * List of constant instances which are generated as JAVA public static final attributes.
57      */
58     protected val List<Constant> consts
59
60     protected val GeneratedTransferObject genTO
61
62     val AbstractRangeGenerator<?> rangeGenerator
63
64     /**
65      * Creates instance of this class with concrete <code>genType</code>.
66      *
67      * @param genType generated transfer object which will be transformed to JAVA class source code
68      */
69     new(GeneratedTransferObject genType) {
70         this(new TopLevelJavaGeneratedType(genType), genType)
71     }
72
73     /**
74      * Creates instance of this class with concrete <code>genType</code>.
75      *
76      * @param genType generated transfer object which will be transformed to JAVA class source code
77      */
78     new(AbstractJavaGeneratedType javaType, GeneratedTransferObject genType) {
79         super(javaType, genType)
80         this.genTO = genType
81         this.properties = genType.properties
82         this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
83         this.parentProperties = GeneratorUtil.getPropertiesOfAllParents(genTO)
84         this.restrictions = genType.restrictions
85
86         val sorted = new ArrayList();
87         sorted.addAll(properties);
88         sorted.addAll(parentProperties);
89         sorted.sort(PROP_COMPARATOR);
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.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.size == 1 && allProperties.get(0).name.equals("value")»
225             «CodeHelpers.importedName».requireValue(_value);
226             «genPatternEnforcer("_value")»
227         «ENDIF»
228
229         «FOR p : properties»
230             «val fieldName = p.fieldName»
231             «IF p.returnType.name.endsWith("[]")»
232                 «IF genTO.typedef && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
233                 this.«fieldName» = «fieldName».clone();
234                 «ELSE»
235                 this.«fieldName» = «fieldName» == null ? null : «fieldName».clone();
236                 «ENDIF»
237             «ELSE»
238             this.«fieldName» = «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, Iterable<GeneratedProperty> other) '''
256     public «type.name»(«property.returnType.importedName + " " + property.name») {
257         «IF false == parentProperties.empty»
258             super(«parentProperties.asArguments»);
259         «ENDIF»
260
261         «val fieldName = property.fieldName»
262         «generateRestrictions(type, fieldName.toString, property.returnType)»
263
264         this.«fieldName» = «property.name»;
265         «FOR p : other»
266             this.«p.fieldName» = null;
267         «ENDFOR»
268     }
269     '''
270
271     def private genPatternEnforcer(String ref) '''
272         «FOR c : consts»
273             «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
274             «CodeHelpers.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
275             «ENDIF»
276         «ENDFOR»
277     '''
278
279     def private static paramValue(Type returnType, String paramName) {
280         if (returnType instanceof ConcreteType) {
281             return paramName
282         } else {
283             return paramName + ".getValue()"
284         }
285     }
286
287     def private generateRestrictions(Type type, String paramName, Type returnType) '''
288         «val restrictions = type.restrictions»
289         «IF restrictions !== null»
290             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
291             if («paramName» != null) {
292                 «IF restrictions.lengthConstraint.present»
293                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
294                 «ENDIF»
295                 «IF restrictions.rangeConstraint.present»
296                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
297                 «ENDIF»
298             }
299             «ENDIF»
300         «ENDIF»
301     '''
302
303     def protected copyConstructor() '''
304     /**
305      * Creates a copy from Source Object.
306      *
307      * @param source Source object
308      */
309     public «type.name»(«type.name» source) {
310         «IF false == parentProperties.empty»
311             super(source);
312         «ENDIF»
313         «FOR p : properties»
314             «val fieldName = p.fieldName»
315             this.«fieldName» = source.«fieldName»;
316         «ENDFOR»
317     }
318     '''
319
320     def protected parentConstructor() '''
321     /**
322      * Creates a new instance from «genTO.superType.importedName»
323      *
324      * @param source Source object
325      */
326     public «type.name»(«genTO.superType.importedName» source) {
327         super(source);
328         «genPatternEnforcer("getValue()")»
329     }
330     '''
331
332     def protected defaultInstance() '''
333         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
334             «val prop = allProperties.get(0)»
335             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
336             public static «genTO.name» getDefaultInstance(String defaultValue) {
337                 «IF BYTE_ARRAY.equals(prop.returnType)»
338                     return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
339                 «ELSEIF STRING.equals(prop.returnType)»
340                     return new «genTO.name»(defaultValue);
341                 «ELSEIF Constants.EMPTY.equals(prop.returnType)»
342                     «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
343                     return new «genTO.name»(«Empty.importedName».getInstance());
344                 «ELSEIF allProperties.size > 1»
345                     «bitsArgs»
346                 «ELSEIF BOOLEAN.equals(prop.returnType)»
347                     return new «genTO.name»(«Boolean.importedName».valueOf(defaultValue));
348                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
349                     return new «genTO.name»(«Byte.importedName».valueOf(defaultValue));
350                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
351                     return new «genTO.name»(«Short.importedName».valueOf(defaultValue));
352                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
353                     return new «genTO.name»(«Integer.importedName».valueOf(defaultValue));
354                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
355                     return new «genTO.name»(«Long.importedName».valueOf(defaultValue));
356                 «ELSE»
357                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
358                 «ENDIF»
359             }
360             «ENDIF»
361         «ENDIF»
362     '''
363
364     def protected bitsArgs() '''
365         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
366         if (!properties.contains(defaultValue)) {
367             throw new «IllegalArgumentException.importedName»("invalid default parameter");
368         }
369         int i = 0;
370         return new «genTO.name»(
371         «FOR prop : allProperties SEPARATOR ","»
372             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
373         «ENDFOR»
374         );
375     '''
376
377     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
378         «FOR prop : properties SEPARATOR ","»
379             "«prop.name»"
380         «ENDFOR»
381     '''
382
383     /**
384      * Template method which generates JAVA class declaration.
385      *
386      * @param isInnerClass boolean value which specify if generated class is|isn't inner
387      * @return string with class declaration in JAVA format
388      */
389     def protected generateClassDeclaration(boolean isInnerClass) '''
390         public«
391         IF (isInnerClass)»«
392             " static final "»«
393         ELSEIF (type.abstract)»«
394             " abstract "»«
395         ELSE»«
396             " "»«
397         ENDIF»class «type.name»«
398         IF (genTO.superType !== null)»«
399             " extends "»«genTO.superType.importedName»«
400         ENDIF»
401         «IF (!type.implements.empty)»«
402             " implements "»«
403             FOR type : type.implements SEPARATOR ", "»«
404                 type.importedName»«
405             ENDFOR»«
406         ENDIF
407     »'''
408
409     /**
410      * Template method which generates JAVA enum type.
411      *
412      * @return string with inner enum source code in JAVA format
413      */
414     def protected enumDeclarations() '''
415         «IF !enums.empty»
416             «FOR e : enums SEPARATOR "\n"»
417                 «new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
418             «ENDFOR»
419         «ENDIF»
420     '''
421
422     def protected suidDeclaration() '''
423         «IF genTO.SUID !== null»
424             private static final long serialVersionUID = «genTO.SUID.value»L;
425         «ENDIF»
426     '''
427
428     def protected annotationDeclaration() '''
429         «IF genTO.getAnnotations !== null»
430             «FOR e : genTO.getAnnotations»
431                 @«e.getName»
432             «ENDFOR»
433         «ENDIF»
434     '''
435
436     /**
437      * Template method which generates JAVA constants.
438      *
439      * @return string with constants in JAVA format
440      */
441     def protected constantsDeclarations() '''
442         «IF !consts.empty»
443             «FOR c : consts»
444                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
445                     «val cValue = c.value as Map<String, String>»
446                     public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
447                     FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
448                     «IF cValue.size == 1»
449                         private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST» = «Pattern.importedName».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
450                         private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.iterator.next.escapeJava»";
451                     «ELSE»
452                         private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST» = «CodeHelpers.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
453                         private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
454                         FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
455                     «ENDIF»
456                 «ELSE»
457                     «emitConstant(c)»
458                 «ENDIF»
459             «ENDFOR»
460         «ENDIF»
461     '''
462
463     /**
464      * Template method which generates JAVA class attributes.
465      *
466      * @return string with the class attributes in JAVA format
467      */
468     def protected generateFields() '''
469         «IF !properties.empty»
470             «FOR f : properties»
471                 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
472             «ENDFOR»
473         «ENDIF»
474     '''
475
476     protected def isReadOnly(GeneratedProperty field) {
477         return field.readOnly
478     }
479
480     /**
481      * Template method which generates the method <code>hashCode()</code>.
482      *
483      * @return string with the <code>hashCode()</code> method definition in JAVA format
484      */
485     def protected generateHashCode() {
486         val size = genTO.hashCodeIdentifiers.size
487         if (size == 0) {
488             return ""
489         }
490         return '''
491             @«Override.importedName»
492             public int hashCode() {
493                 «IF size != 1»
494                     «hashCodeResult(genTO.hashCodeIdentifiers)»
495                     return result;
496                 «ELSE»
497                     return «CodeHelpers.importedName».wrapperHashCode(«genTO.hashCodeIdentifiers.get(0).fieldName»);
498                 «ENDIF»
499             }
500         '''
501     }
502
503     /**
504      * Template method which generates the method <code>equals()</code>.
505      *
506      * @return string with the <code>equals()</code> method definition in JAVA format
507      */
508     def protected generateEquals() '''
509         «IF !genTO.equalsIdentifiers.empty»
510             @«Override.importedName»
511             public boolean equals(java.lang.Object obj) {
512                 if (this == obj) {
513                     return true;
514                 }
515                 if (obj == null) {
516                     return false;
517                 }
518                 if (getClass() != obj.getClass()) {
519                     return false;
520                 }
521                 «type.name» other = («type.name») obj;
522                 «FOR property : genTO.equalsIdentifiers»
523                     «val fieldName = property.fieldName»
524                     if (!«property.importedUtilClass».equals(«fieldName», other.«fieldName»)) {
525                         return false;
526                     }
527                 «ENDFOR»
528                 return true;
529             }
530         «ENDIF»
531     '''
532
533     def GeneratedProperty getPropByName(String name) {
534         for (GeneratedProperty prop : allProperties) {
535             if (prop.name.equals(name)) {
536                 return prop;
537             }
538         }
539         return null;
540     }
541
542 }