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