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