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