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