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