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