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