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