08935e81aa35ddfa171daa7da35c4d0790ce9b84
[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 import static extension org.opendaylight.mdsal.binding.model.ri.BindingTypes.isBitsType
30
31 import com.google.common.base.Preconditions
32 import com.google.common.collect.ImmutableList
33 import com.google.common.collect.Lists
34 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
35 import java.beans.ConstructorProperties
36 import java.util.ArrayList
37 import java.util.Base64;
38 import java.util.Collection
39 import java.util.Comparator
40 import java.util.List
41 import java.util.Map
42 import java.util.Set
43 import javax.management.ConstructorParameters
44 import org.gaul.modernizer_maven_annotations.SuppressModernizer
45 import org.opendaylight.mdsal.binding.model.api.ConcreteType
46 import org.opendaylight.mdsal.binding.model.api.Constant
47 import org.opendaylight.mdsal.binding.model.api.Enumeration
48 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
49 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
50 import org.opendaylight.mdsal.binding.model.api.Restrictions
51 import org.opendaylight.mdsal.binding.model.api.Type
52 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
53 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
54 import org.opendaylight.yangtools.yang.common.Empty
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.isBitsType»
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 }