Generate legacy value contructors for all classes
[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         «ELSEIF genTO.typedef && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
205             «typedefConstructor»
206             «legacyConstructor»
207         «ELSE»
208             «allValuesConstructor»
209             «legacyConstructor»
210         «ENDIF»
211
212         «IF !allProperties.empty»
213             «copyConstructor»
214         «ENDIF»
215         «IF properties.empty && !parentProperties.empty »
216             «parentConstructor»
217         «ENDIF»
218     '''
219
220     def private allValuesConstructor() '''
221     public «type.name»(«allProperties.asArgumentsDeclaration») {
222         «IF false == parentProperties.empty»
223             super(«parentProperties.asArguments»);
224         «ENDIF»
225         «FOR p : allProperties»
226             «generateRestrictions(type, p.fieldName, p.returnType)»
227         «ENDFOR»
228
229         «FOR p : properties»
230             «val fieldName = p.fieldName»
231             «IF p.returnType.name.endsWith("[]")»
232                 this.«fieldName» = «fieldName» == null ? null : «fieldName».clone();
233             «ELSE»
234                 this.«fieldName» = «fieldName»;
235             «ENDIF»
236         «ENDFOR»
237     }
238     '''
239
240     def private typedefConstructor() '''
241     @«ConstructorParameters.importedName»("value")
242     @«ConstructorProperties.importedName»("value")
243     public «type.name»(«allProperties.asArgumentsDeclaration») {
244         «IF false == parentProperties.empty»
245             super(«parentProperties.asArguments»);
246         «ENDIF»
247         «FOR p : allProperties»
248             «generateRestrictions(type, p.fieldName, p.returnType)»
249         «ENDFOR»
250         «/*
251          * If we have patterns, we need to apply them to the value field. This is a sad consequence of how this code is
252          * structured.
253          */»
254         «CodeHelpers.importedName».requireValue(_value);
255         «genPatternEnforcer("_value")»
256
257         «FOR p : properties»
258             «val fieldName = p.fieldName»
259             «IF p.returnType.name.endsWith("[]")»
260                 this.«fieldName» = «fieldName».clone();
261             «ELSE»
262                 this.«fieldName» = «fieldName»;
263             «ENDIF»
264         «ENDFOR»
265     }
266     '''
267
268     def private legacyConstructor() {
269         if (!hasUintProperties) {
270             return ""
271         }
272
273         val compatUint = CodeHelpers.importedName + ".compatUint("
274         return '''
275
276             /**
277              * Utility migration constructor.
278              *
279              «FOR prop : allProperties»
280              * @param «prop.fieldName» «prop.name»«IF prop.isUintType» in legacy Java type«ENDIF»
281              «ENDFOR»
282              * @deprecated Use {#link «type.name»(«FOR prop : allProperties SEPARATOR ", "»«prop.returnType.importedName»«ENDFOR»)} instead.
283              */
284             @Deprecated(forRemoval = true)
285             public «type.getName»(«FOR prop : allProperties SEPARATOR ", "»«prop.legacyType.importedName» «prop.fieldName»«ENDFOR») {
286                 this(«FOR prop : allProperties SEPARATOR ", "»«IF prop.isUintType»«compatUint»«prop.fieldName»)«ELSE»«prop.fieldName»«ENDIF»«ENDFOR»);
287             }
288         '''
289     }
290
291     def protected genUnionConstructor() '''
292     «FOR p : allProperties»
293         «val List<GeneratedProperty> other = new ArrayList(properties)»
294         «IF other.remove(p)»
295             «genConstructor(p, other)»
296         «ENDIF»
297     «ENDFOR»
298     '''
299
300     def protected genConstructor(GeneratedProperty property, Iterable<GeneratedProperty> other) '''
301     public «type.name»(«property.returnType.importedName + " " + property.name») {
302         «IF false == parentProperties.empty»
303             super(«parentProperties.asArguments»);
304         «ENDIF»
305
306         «val fieldName = property.fieldName»
307         «generateRestrictions(type, fieldName, property.returnType)»
308
309         this.«fieldName» = «property.name»;
310         «FOR p : other»
311             this.«p.fieldName» = null;
312         «ENDFOR»
313     }
314     '''
315
316     def private genPatternEnforcer(String ref) '''
317         «FOR c : consts»
318             «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
319             «CodeHelpers.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
320             «ENDIF»
321         «ENDFOR»
322     '''
323
324     def private static paramValue(Type returnType, String paramName) {
325         if (returnType instanceof ConcreteType) {
326             return paramName
327         } else {
328             return paramName + ".getValue()"
329         }
330     }
331
332     def private generateRestrictions(Type type, String paramName, Type returnType) '''
333         «val restrictions = type.restrictions»
334         «IF restrictions !== null»
335             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
336             if («paramName» != null) {
337                 «IF restrictions.lengthConstraint.present»
338                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
339                 «ENDIF»
340                 «IF restrictions.rangeConstraint.present»
341                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
342                 «ENDIF»
343             }
344             «ENDIF»
345         «ENDIF»
346     '''
347
348     def protected copyConstructor() '''
349     /**
350      * Creates a copy from Source Object.
351      *
352      * @param source Source object
353      */
354     public «type.name»(«type.name» source) {
355         «IF false == parentProperties.empty»
356             super(source);
357         «ENDIF»
358         «FOR p : properties»
359             «val fieldName = p.fieldName»
360             this.«fieldName» = source.«fieldName»;
361         «ENDFOR»
362     }
363     '''
364
365     def protected parentConstructor() '''
366     /**
367      * Creates a new instance from «genTO.superType.importedName»
368      *
369      * @param source Source object
370      */
371     public «type.name»(«genTO.superType.importedName» source) {
372         super(source);
373         «genPatternEnforcer("getValue()")»
374     }
375     '''
376
377     def protected defaultInstance() '''
378         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
379             «val prop = allProperties.get(0)»
380             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
381             public static «genTO.name» getDefaultInstance(String defaultValue) {
382                 «IF BYTE_ARRAY.equals(prop.returnType)»
383                     return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
384                 «ELSEIF STRING.equals(prop.returnType)»
385                     return new «genTO.name»(defaultValue);
386                 «ELSEIF Constants.EMPTY.equals(prop.returnType)»
387                     «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
388                     return new «genTO.name»(«Empty.importedName».getInstance());
389                 «ELSEIF allProperties.size > 1»
390                     «bitsArgs»
391                 «ELSEIF BOOLEAN.equals(prop.returnType)»
392                     return new «genTO.name»(«Boolean.importedName».valueOf(defaultValue));
393                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
394                     return new «genTO.name»(«Byte.importedName».valueOf(defaultValue));
395                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
396                     return new «genTO.name»(«Short.importedName».valueOf(defaultValue));
397                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
398                     return new «genTO.name»(«Integer.importedName».valueOf(defaultValue));
399                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
400                     return new «genTO.name»(«Long.importedName».valueOf(defaultValue));
401                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint8".equals(prop.returnType.fullyQualifiedName)»
402                     return new «genTO.name»(«Uint8.importedName».valueOf(defaultValue));
403                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint16".equals(prop.returnType.fullyQualifiedName)»
404                     return new «genTO.name»(«Uint16.importedName».valueOf(defaultValue));
405                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint32".equals(prop.returnType.fullyQualifiedName)»
406                     return new «genTO.name»(«Uint32.importedName».valueOf(defaultValue));
407                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint64".equals(prop.returnType.fullyQualifiedName)»
408                     return new «genTO.name»(«Uint64.importedName».valueOf(defaultValue));
409                 «ELSE»
410                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
411                 «ENDIF»
412             }
413             «ENDIF»
414         «ENDIF»
415     '''
416
417     def protected bitsArgs() '''
418         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
419         if (!properties.contains(defaultValue)) {
420             throw new «IllegalArgumentException.importedName»("invalid default parameter");
421         }
422         int i = 0;
423         return new «genTO.name»(
424         «FOR prop : allProperties SEPARATOR ","»
425             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
426         «ENDFOR»
427         );
428     '''
429
430     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
431         «FOR prop : properties SEPARATOR ","»
432             "«prop.name»"
433         «ENDFOR»
434     '''
435
436     /**
437      * Template method which generates JAVA class declaration.
438      *
439      * @param isInnerClass boolean value which specify if generated class is|isn't inner
440      * @return string with class declaration in JAVA format
441      */
442     def protected generateClassDeclaration(boolean isInnerClass) '''
443         public«
444         IF (isInnerClass)»«
445             " static final "»«
446         ELSEIF (type.abstract)»«
447             " abstract "»«
448         ELSE»«
449             " "»«
450         ENDIF»class «type.name»«
451         IF (genTO.superType !== null)»«
452             " extends "»«genTO.superType.importedName»«
453         ENDIF»
454         «IF (!type.implements.empty)»«
455             " implements "»«
456             FOR type : type.implements SEPARATOR ", "»«
457                 type.importedName»«
458             ENDFOR»«
459         ENDIF
460     »'''
461
462     /**
463      * Template method which generates JAVA enum type.
464      *
465      * @return string with inner enum source code in JAVA format
466      */
467     def protected enumDeclarations() '''
468         «IF !enums.empty»
469             «FOR e : enums SEPARATOR "\n"»
470                 «new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
471             «ENDFOR»
472         «ENDIF»
473     '''
474
475     def protected suidDeclaration() '''
476         «IF genTO.SUID !== null»
477             private static final long serialVersionUID = «genTO.SUID.value»L;
478         «ENDIF»
479     '''
480
481     def protected annotationDeclaration() '''
482         «IF genTO.getAnnotations !== null»
483             «FOR e : genTO.getAnnotations»
484                 @«e.getName»
485             «ENDFOR»
486         «ENDIF»
487     '''
488
489     /**
490      * Template method which generates JAVA constants.
491      *
492      * @return string with constants in JAVA format
493      */
494     def protected constantsDeclarations() '''
495         «IF !consts.empty»
496             «FOR c : consts»
497                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
498                     «val cValue = c.value as Map<String, String>»
499                     public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
500                     FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
501                     «IF cValue.size == 1»
502                         private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST» = «Pattern.importedName».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
503                         private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.iterator.next.escapeJava»";
504                     «ELSE»
505                         private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST» = «CodeHelpers.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
506                         private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
507                         FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
508                     «ENDIF»
509                 «ELSE»
510                     «emitConstant(c)»
511                 «ENDIF»
512             «ENDFOR»
513         «ENDIF»
514     '''
515
516     /**
517      * Template method which generates JAVA class attributes.
518      *
519      * @return string with the class attributes in JAVA format
520      */
521     def protected generateFields() '''
522         «IF !properties.empty»
523             «FOR f : properties»
524                 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
525             «ENDFOR»
526         «ENDIF»
527     '''
528
529     protected def isReadOnly(GeneratedProperty field) {
530         return field.readOnly
531     }
532
533     /**
534      * Template method which generates the method <code>hashCode()</code>.
535      *
536      * @return string with the <code>hashCode()</code> method definition in JAVA format
537      */
538     def protected generateHashCode() {
539         val size = genTO.hashCodeIdentifiers.size
540         if (size == 0) {
541             return ""
542         }
543         return '''
544             @«Override.importedName»
545             public int hashCode() {
546                 «IF size != 1»
547                     «hashCodeResult(genTO.hashCodeIdentifiers)»
548                     return result;
549                 «ELSE»
550                     return «CodeHelpers.importedName».wrapperHashCode(«genTO.hashCodeIdentifiers.get(0).fieldName»);
551                 «ENDIF»
552             }
553         '''
554     }
555
556     /**
557      * Template method which generates the method <code>equals()</code>.
558      *
559      * @return string with the <code>equals()</code> method definition in JAVA format
560      */
561     def private generateEquals() '''
562         «IF !genTO.equalsIdentifiers.empty»
563             @«Override.importedName»
564             public final boolean equals(java.lang.Object obj) {
565                 if (this == obj) {
566                     return true;
567                 }
568                 if (!(obj instanceof «type.name»)) {
569                     return false;
570                 }
571                 final «type.name» other = («type.name») obj;
572                 «FOR property : genTO.equalsIdentifiers»
573                     «val fieldName = property.fieldName»
574                     if (!«property.importedUtilClass».equals(«fieldName», other.«fieldName»)) {
575                         return false;
576                     }
577                 «ENDFOR»
578                 return true;
579             }
580         «ENDIF»
581     '''
582
583     def GeneratedProperty getPropByName(String name) {
584         for (GeneratedProperty prop : allProperties) {
585             if (prop.name.equals(name)) {
586                 return prop;
587             }
588         }
589         return null
590     }
591
592     def private hasUintProperties() {
593         for (GeneratedProperty prop : allProperties) {
594             if (prop.isUintType) {
595                 return true
596             }
597         }
598         return false
599     }
600
601     def private static isUintType(GeneratedProperty prop) {
602         UINT_TYPES.containsKey(prop.returnType)
603     }
604
605     def private static legacyType(GeneratedProperty prop) {
606         val type = prop.returnType
607         val uint = UINT_TYPES.get(type)
608         if (uint !== null) {
609             return uint
610         }
611         return type
612     }
613 }