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