3bc5fa6abf50801f06b4fc4ea9ca370dc18f9c86
[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         «ELSEIF genTO.typedef && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
205             «typedefConstructor»
206         «ELSE»
207             «allValuesConstructor»
208         «ENDIF»
209
210         «IF !allProperties.empty»
211             «copyConstructor»
212         «ENDIF»
213         «IF properties.empty && !parentProperties.empty »
214             «parentConstructor»
215         «ENDIF»
216     '''
217
218     def private allValuesConstructor() '''
219     public «type.name»(«allProperties.asArgumentsDeclaration») {
220         «IF false == parentProperties.empty»
221             super(«parentProperties.asArguments»);
222         «ENDIF»
223         «FOR p : allProperties»
224             «generateRestrictions(type, p.fieldName.toString, p.returnType)»
225         «ENDFOR»
226
227         «FOR p : properties»
228             «val fieldName = p.fieldName»
229             «IF p.returnType.name.endsWith("[]")»
230                 this.«fieldName» = «fieldName» == null ? null : «fieldName».clone();
231             «ELSE»
232                 this.«fieldName» = «fieldName»;
233             «ENDIF»
234         «ENDFOR»
235     }
236     '''
237
238     def private typedefConstructor() '''
239     @«ConstructorParameters.importedName»("value")
240     @«ConstructorProperties.importedName»("value")
241     public «type.name»(«allProperties.asArgumentsDeclaration») {
242         «IF false == parentProperties.empty»
243             super(«parentProperties.asArguments»);
244         «ENDIF»
245         «FOR p : allProperties»
246             «generateRestrictions(type, p.fieldName.toString, p.returnType)»
247         «ENDFOR»
248         «/*
249          * If we have patterns, we need to apply them to the value field. This is a sad consequence of how this code is
250          * structured.
251          */»
252         «CodeHelpers.importedName».requireValue(_value);
253         «genPatternEnforcer("_value")»
254
255         «FOR p : properties»
256             «val fieldName = p.fieldName»
257             «IF p.returnType.name.endsWith("[]")»
258                 this.«fieldName» = «fieldName».clone();
259             «ELSE»
260                 this.«fieldName» = «fieldName»;
261             «ENDIF»
262         «ENDFOR»
263     }
264     '''
265
266     def protected genUnionConstructor() '''
267     «FOR p : allProperties»
268         «val List<GeneratedProperty> other = new ArrayList(properties)»
269         «IF other.remove(p)»
270             «genConstructor(p, other)»
271         «ENDIF»
272     «ENDFOR»
273     '''
274
275     def protected genConstructor(GeneratedProperty property, Iterable<GeneratedProperty> other) '''
276     public «type.name»(«property.returnType.importedName + " " + property.name») {
277         «IF false == parentProperties.empty»
278             super(«parentProperties.asArguments»);
279         «ENDIF»
280
281         «val fieldName = property.fieldName»
282         «generateRestrictions(type, fieldName.toString, property.returnType)»
283
284         this.«fieldName» = «property.name»;
285         «FOR p : other»
286             this.«p.fieldName» = null;
287         «ENDFOR»
288     }
289     '''
290
291     def private genPatternEnforcer(String ref) '''
292         «FOR c : consts»
293             «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
294             «CodeHelpers.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
295             «ENDIF»
296         «ENDFOR»
297     '''
298
299     def private static paramValue(Type returnType, String paramName) {
300         if (returnType instanceof ConcreteType) {
301             return paramName
302         } else {
303             return paramName + ".getValue()"
304         }
305     }
306
307     def private generateRestrictions(Type type, String paramName, Type returnType) '''
308         «val restrictions = type.restrictions»
309         «IF restrictions !== null»
310             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
311             if («paramName» != null) {
312                 «IF restrictions.lengthConstraint.present»
313                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
314                 «ENDIF»
315                 «IF restrictions.rangeConstraint.present»
316                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
317                 «ENDIF»
318             }
319             «ENDIF»
320         «ENDIF»
321     '''
322
323     def protected copyConstructor() '''
324     /**
325      * Creates a copy from Source Object.
326      *
327      * @param source Source object
328      */
329     public «type.name»(«type.name» source) {
330         «IF false == parentProperties.empty»
331             super(source);
332         «ENDIF»
333         «FOR p : properties»
334             «val fieldName = p.fieldName»
335             this.«fieldName» = source.«fieldName»;
336         «ENDFOR»
337     }
338     '''
339
340     def protected parentConstructor() '''
341     /**
342      * Creates a new instance from «genTO.superType.importedName»
343      *
344      * @param source Source object
345      */
346     public «type.name»(«genTO.superType.importedName» source) {
347         super(source);
348         «genPatternEnforcer("getValue()")»
349     }
350     '''
351
352     def protected defaultInstance() '''
353         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
354             «val prop = allProperties.get(0)»
355             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
356             public static «genTO.name» getDefaultInstance(String defaultValue) {
357                 «IF BYTE_ARRAY.equals(prop.returnType)»
358                     return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
359                 «ELSEIF STRING.equals(prop.returnType)»
360                     return new «genTO.name»(defaultValue);
361                 «ELSEIF Constants.EMPTY.equals(prop.returnType)»
362                     «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
363                     return new «genTO.name»(«Empty.importedName».getInstance());
364                 «ELSEIF allProperties.size > 1»
365                     «bitsArgs»
366                 «ELSEIF BOOLEAN.equals(prop.returnType)»
367                     return new «genTO.name»(«Boolean.importedName».valueOf(defaultValue));
368                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
369                     return new «genTO.name»(«Byte.importedName».valueOf(defaultValue));
370                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
371                     return new «genTO.name»(«Short.importedName».valueOf(defaultValue));
372                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
373                     return new «genTO.name»(«Integer.importedName».valueOf(defaultValue));
374                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
375                     return new «genTO.name»(«Long.importedName».valueOf(defaultValue));
376                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint8".equals(prop.returnType.fullyQualifiedName)»
377                     return new «genTO.name»(«Uint8.importedName».valueOf(defaultValue));
378                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint16".equals(prop.returnType.fullyQualifiedName)»
379                     return new «genTO.name»(«Uint16.importedName».valueOf(defaultValue));
380                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint32".equals(prop.returnType.fullyQualifiedName)»
381                     return new «genTO.name»(«Uint32.importedName».valueOf(defaultValue));
382                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint64".equals(prop.returnType.fullyQualifiedName)»
383                     return new «genTO.name»(«Uint64.importedName».valueOf(defaultValue));
384                 «ELSE»
385                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
386                 «ENDIF»
387             }
388             «ENDIF»
389         «ENDIF»
390     '''
391
392     def protected bitsArgs() '''
393         «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                     public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
475                     FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
476                     «IF cValue.size == 1»
477                         private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST» = «Pattern.importedName».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
478                         private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.iterator.next.escapeJava»";
479                     «ELSE»
480                         private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST» = «CodeHelpers.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
481                         private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
482                         FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
483                     «ENDIF»
484                 «ELSE»
485                     «emitConstant(c)»
486                 «ENDIF»
487             «ENDFOR»
488         «ENDIF»
489     '''
490
491     /**
492      * Template method which generates JAVA class attributes.
493      *
494      * @return string with the class attributes in JAVA format
495      */
496     def protected generateFields() '''
497         «IF !properties.empty»
498             «FOR f : properties»
499                 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
500             «ENDFOR»
501         «ENDIF»
502     '''
503
504     protected def isReadOnly(GeneratedProperty field) {
505         return field.readOnly
506     }
507
508     /**
509      * Template method which generates the method <code>hashCode()</code>.
510      *
511      * @return string with the <code>hashCode()</code> method definition in JAVA format
512      */
513     def protected generateHashCode() {
514         val size = genTO.hashCodeIdentifiers.size
515         if (size == 0) {
516             return ""
517         }
518         return '''
519             @«Override.importedName»
520             public int hashCode() {
521                 «IF size != 1»
522                     «hashCodeResult(genTO.hashCodeIdentifiers)»
523                     return result;
524                 «ELSE»
525                     return «CodeHelpers.importedName».wrapperHashCode(«genTO.hashCodeIdentifiers.get(0).fieldName»);
526                 «ENDIF»
527             }
528         '''
529     }
530
531     /**
532      * Template method which generates the method <code>equals()</code>.
533      *
534      * @return string with the <code>equals()</code> method definition in JAVA format
535      */
536     def private generateEquals() '''
537         «IF !genTO.equalsIdentifiers.empty»
538             @«Override.importedName»
539             public final boolean equals(java.lang.Object obj) {
540                 if (this == obj) {
541                     return true;
542                 }
543                 if (!(obj instanceof «type.name»)) {
544                     return false;
545                 }
546                 final «type.name» other = («type.name») obj;
547                 «FOR property : genTO.equalsIdentifiers»
548                     «val fieldName = property.fieldName»
549                     if (!«property.importedUtilClass».equals(«fieldName», other.«fieldName»)) {
550                         return false;
551                     }
552                 «ENDFOR»
553                 return true;
554             }
555         «ENDIF»
556     '''
557
558     def GeneratedProperty getPropByName(String name) {
559         for (GeneratedProperty prop : allProperties) {
560             if (prop.name.equals(name)) {
561                 return prop;
562             }
563         }
564         return null;
565     }
566
567 }