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