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