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