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