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