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