Change 'type empty' mapping
[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.Objects
26 import java.util.regex.Pattern
27 import org.opendaylight.mdsal.binding.model.api.ConcreteType
28 import org.opendaylight.mdsal.binding.model.api.Constant
29 import org.opendaylight.mdsal.binding.model.api.Enumeration
30 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
31 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
32 import org.opendaylight.mdsal.binding.model.api.Restrictions
33 import org.opendaylight.mdsal.binding.model.api.Type
34 import org.opendaylight.mdsal.binding.model.util.TypeConstants
35 import org.opendaylight.yangtools.yang.binding.CodeHelpers
36 import org.opendaylight.yangtools.yang.common.Empty
37 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
38
39 /**
40  * Template for generating JAVA class.
41  */
42 class ClassTemplate extends BaseTemplate {
43
44     protected val List<GeneratedProperty> properties
45     protected val List<GeneratedProperty> finalProperties
46     protected val List<GeneratedProperty> parentProperties
47     protected val Iterable<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         var List<GeneratedProperty> sorted = new ArrayList<GeneratedProperty>();
87         sorted.addAll(properties);
88         sorted.addAll(parentProperties);
89         Collections.sort(sorted, [p1, p2|
90             p1.name.compareTo(p2.name)
91         ]);
92
93         this.allProperties = sorted
94         this.enums = genType.enumerations
95         this.consts = genType.constantDefinitions
96
97         if (restrictions !== null && restrictions.rangeConstraint.present) {
98             rangeGenerator = requireNonNull(AbstractRangeGenerator.forType(findProperty(genType, "value").returnType))
99         } else {
100             rangeGenerator = null
101         }
102     }
103
104     /**
105      * Generates JAVA class source code (class body only).
106      *
107      * @return string with JAVA class body source code
108      */
109     def CharSequence generateAsInnerClass() {
110         return generateBody(true)
111     }
112
113     override protected body() {
114         generateBody(false);
115     }
116
117     /**
118      * Template method which generates class body.
119      *
120      * @param isInnerClass boolean value which specify if generated class is|isn't inner
121      * @return string with class source code in JAVA format
122      */
123     def protected generateBody(boolean isInnerClass) '''
124         «wrapToDocumentation(formatDataForJavaDoc(type))»
125         «annotationDeclaration»
126         «generateClassDeclaration(isInnerClass)» {
127             «suidDeclaration»
128             «innerClassesDeclarations»
129             «enumDeclarations»
130             «constantsDeclarations»
131             «generateFields»
132
133             «IF restrictions !== null»
134                 «IF restrictions.lengthConstraint.present»
135                     «LengthGenerator.generateLengthChecker("_value", findProperty(genTO, "value").returnType, restrictions.lengthConstraint.get, this)»
136                 «ENDIF»
137                 «IF restrictions.rangeConstraint.present»
138                     «rangeGenerator.generateRangeChecker("_value", restrictions.rangeConstraint.get, this)»
139                 «ENDIF»
140             «ENDIF»
141
142             «constructors»
143
144             «defaultInstance»
145
146             «FOR field : properties SEPARATOR "\n"»
147                 «field.getterMethod»
148                 «IF !field.readOnly»
149                     «field.setterMethod»
150                 «ENDIF»
151             «ENDFOR»
152
153             «IF (genTO.isTypedef() && genTO.getBaseType instanceof BitsTypeDefinition)»
154                 «generateGetValueForBitsTypeDef»
155             «ENDIF»
156
157             «generateHashCode»
158
159             «generateEquals»
160
161             «generateToString(genTO.toStringIdentifiers)»
162         }
163
164     '''
165
166     /**
167      * Template method which generates the method <code>getValue()</code> for typedef,
168      * which base type is BitsDefinition.
169      *
170      * @return string with the <code>getValue()</code> method definition in JAVA format
171      */
172     def protected generateGetValueForBitsTypeDef() '''
173
174         public boolean[] getValue() {
175             return new boolean[]{
176             «FOR property: genTO.properties SEPARATOR ','»
177                  «property.fieldName»
178             «ENDFOR»
179             };
180         }
181     '''
182
183     /**
184      * Template method which generates inner classes inside this interface.
185      *
186      * @return string with the source code for inner classes in JAVA format
187      */
188     def protected innerClassesDeclarations() '''
189         «IF !type.enclosedTypes.empty»
190             «FOR innerClass : type.enclosedTypes SEPARATOR "\n"»
191                 «generateInnerClass(innerClass)»
192             «ENDFOR»
193         «ENDIF»
194     '''
195
196     def protected constructors() '''
197         «IF genTO.unionType»
198             «genUnionConstructor»
199         «ELSE»
200             «allValuesConstructor»
201         «ENDIF»
202         «IF !allProperties.empty»
203             «copyConstructor»
204         «ENDIF»
205         «IF properties.empty && !parentProperties.empty »
206             «parentConstructor»
207         «ENDIF»
208     '''
209
210     def protected allValuesConstructor() '''
211     «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
212         @«ConstructorProperties.importedName»("value")
213     «ENDIF»
214     public «type.name»(«allProperties.asArgumentsDeclaration») {
215         «IF false == parentProperties.empty»
216             super(«parentProperties.asArguments»);
217         «ENDIF»
218         «FOR p : allProperties»
219             «generateRestrictions(type, p.fieldName.toString, p.returnType)»
220         «ENDFOR»
221
222         «/*
223          * If we have patterns, we need to apply them to the value field. This is a sad
224          * consequence of how this code is structured.
225          */
226         IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
227             «Objects.importedName».requireNonNull(_value, "Supplied value may not be null");
228             «genPatternEnforcer("_value")»
229         «ENDIF»
230
231         «FOR p : properties»
232             «IF p.returnType.importedName.contains("[]")»
233                 «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name
234                 .equals("value")»
235                 this.«p.fieldName» = «p.fieldName».clone();
236                 «ELSE»
237                 this.«p.fieldName» = «p.fieldName» == null ? null : «p.fieldName».clone();
238                 «ENDIF»
239             «ELSE»
240             this.«p.fieldName» = «p.fieldName»;
241             «ENDIF»
242         «ENDFOR»
243     }
244
245     '''
246
247     def protected genUnionConstructor() '''
248     «FOR p : allProperties»
249         «val List<GeneratedProperty> other = new ArrayList(properties)»
250         «IF other.remove(p)»
251             «genConstructor(p, other)»
252         «ENDIF»
253     «ENDFOR»
254
255     '''
256
257     def protected genConstructor(GeneratedProperty property, GeneratedProperty... other) '''
258     public «type.name»(«property.returnType.importedName + " " + property.name») {
259         «IF false == parentProperties.empty»
260             super(«parentProperties.asArguments»);
261         «ENDIF»
262
263         «generateRestrictions(type, property.fieldName.toString, property.returnType)»
264
265         this.«property.fieldName» = «property.name»;
266         «FOR p : other»
267             this.«p.fieldName» = null;
268         «ENDFOR»
269     }
270     '''
271
272     def private genPatternEnforcer(String ref) '''
273         «FOR c : consts»
274             «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
275             «CodeHelpers.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
276             «ENDIF»
277         «ENDFOR»
278     '''
279
280     def private static paramValue(Type returnType, String paramName) {
281         if (returnType instanceof ConcreteType) {
282             return paramName
283         } else {
284             return paramName + ".getValue()"
285         }
286     }
287
288     def private generateRestrictions(Type type, String paramName, Type returnType) '''
289         «val restrictions = type.restrictions»
290         «IF restrictions !== null»
291             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
292             if («paramName» != null) {
293                 «IF restrictions.lengthConstraint.present»
294                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
295                 «ENDIF»
296                 «IF restrictions.rangeConstraint.present»
297                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
298                 «ENDIF»
299             }
300             «ENDIF»
301         «ENDIF»
302     '''
303
304     def protected copyConstructor() '''
305     /**
306      * Creates a copy from Source Object.
307      *
308      * @param source Source object
309      */
310     public «type.name»(«type.name» source) {
311         «IF false == parentProperties.empty»
312             super(source);
313         «ENDIF»
314         «FOR p : properties»
315             this.«p.fieldName» = source.«p.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.get(0).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 }