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