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