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