Make BaseTemplate.fieldName() return String
[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, 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, 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     «val propType = allProperties.get(0).returnType»
265     «val uintType = UINT_TYPES.get(propType)»
266     «IF uintType !== null»
267
268         /**
269          * Utility migration constructor.
270          *
271          * @param value Wrapped value in legacy type
272          * @deprecated Use {#link «type.name»(«propType.importedName»)} instead.
273          */
274         @Deprecated(forRemoval = true)
275         public «type.getName»(final «uintType.importedName» value) {
276             this(«CodeHelpers.importedName».compatUint(value));
277         }
278     «ENDIF»
279     '''
280
281     def protected genUnionConstructor() '''
282     «FOR p : allProperties»
283         «val List<GeneratedProperty> other = new ArrayList(properties)»
284         «IF other.remove(p)»
285             «genConstructor(p, other)»
286         «ENDIF»
287     «ENDFOR»
288     '''
289
290     def protected genConstructor(GeneratedProperty property, Iterable<GeneratedProperty> other) '''
291     public «type.name»(«property.returnType.importedName + " " + property.name») {
292         «IF false == parentProperties.empty»
293             super(«parentProperties.asArguments»);
294         «ENDIF»
295
296         «val fieldName = property.fieldName»
297         «generateRestrictions(type, fieldName, property.returnType)»
298
299         this.«fieldName» = «property.name»;
300         «FOR p : other»
301             this.«p.fieldName» = null;
302         «ENDFOR»
303     }
304     '''
305
306     def private genPatternEnforcer(String ref) '''
307         «FOR c : consts»
308             «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
309             «CodeHelpers.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
310             «ENDIF»
311         «ENDFOR»
312     '''
313
314     def private static paramValue(Type returnType, String paramName) {
315         if (returnType instanceof ConcreteType) {
316             return paramName
317         } else {
318             return paramName + ".getValue()"
319         }
320     }
321
322     def private generateRestrictions(Type type, String paramName, Type returnType) '''
323         «val restrictions = type.restrictions»
324         «IF restrictions !== null»
325             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
326             if («paramName» != null) {
327                 «IF restrictions.lengthConstraint.present»
328                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
329                 «ENDIF»
330                 «IF restrictions.rangeConstraint.present»
331                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
332                 «ENDIF»
333             }
334             «ENDIF»
335         «ENDIF»
336     '''
337
338     def protected copyConstructor() '''
339     /**
340      * Creates a copy from Source Object.
341      *
342      * @param source Source object
343      */
344     public «type.name»(«type.name» source) {
345         «IF false == parentProperties.empty»
346             super(source);
347         «ENDIF»
348         «FOR p : properties»
349             «val fieldName = p.fieldName»
350             this.«fieldName» = source.«fieldName»;
351         «ENDFOR»
352     }
353     '''
354
355     def protected parentConstructor() '''
356     /**
357      * Creates a new instance from «genTO.superType.importedName»
358      *
359      * @param source Source object
360      */
361     public «type.name»(«genTO.superType.importedName» source) {
362         super(source);
363         «genPatternEnforcer("getValue()")»
364     }
365     '''
366
367     def protected defaultInstance() '''
368         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
369             «val prop = allProperties.get(0)»
370             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
371             public static «genTO.name» getDefaultInstance(String defaultValue) {
372                 «IF BYTE_ARRAY.equals(prop.returnType)»
373                     return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
374                 «ELSEIF STRING.equals(prop.returnType)»
375                     return new «genTO.name»(defaultValue);
376                 «ELSEIF Constants.EMPTY.equals(prop.returnType)»
377                     «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
378                     return new «genTO.name»(«Empty.importedName».getInstance());
379                 «ELSEIF allProperties.size > 1»
380                     «bitsArgs»
381                 «ELSEIF BOOLEAN.equals(prop.returnType)»
382                     return new «genTO.name»(«Boolean.importedName».valueOf(defaultValue));
383                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
384                     return new «genTO.name»(«Byte.importedName».valueOf(defaultValue));
385                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
386                     return new «genTO.name»(«Short.importedName».valueOf(defaultValue));
387                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
388                     return new «genTO.name»(«Integer.importedName».valueOf(defaultValue));
389                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
390                     return new «genTO.name»(«Long.importedName».valueOf(defaultValue));
391                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint8".equals(prop.returnType.fullyQualifiedName)»
392                     return new «genTO.name»(«Uint8.importedName».valueOf(defaultValue));
393                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint16".equals(prop.returnType.fullyQualifiedName)»
394                     return new «genTO.name»(«Uint16.importedName».valueOf(defaultValue));
395                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint32".equals(prop.returnType.fullyQualifiedName)»
396                     return new «genTO.name»(«Uint32.importedName».valueOf(defaultValue));
397                 «ELSEIF "org.opendaylight.yangtools.yang.common.Uint64".equals(prop.returnType.fullyQualifiedName)»
398                     return new «genTO.name»(«Uint64.importedName».valueOf(defaultValue));
399                 «ELSE»
400                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
401                 «ENDIF»
402             }
403             «ENDIF»
404         «ENDIF»
405     '''
406
407     def protected bitsArgs() '''
408         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
409         if (!properties.contains(defaultValue)) {
410             throw new «IllegalArgumentException.importedName»("invalid default parameter");
411         }
412         int i = 0;
413         return new «genTO.name»(
414         «FOR prop : allProperties SEPARATOR ","»
415             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
416         «ENDFOR»
417         );
418     '''
419
420     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
421         «FOR prop : properties SEPARATOR ","»
422             "«prop.name»"
423         «ENDFOR»
424     '''
425
426     /**
427      * Template method which generates JAVA class declaration.
428      *
429      * @param isInnerClass boolean value which specify if generated class is|isn't inner
430      * @return string with class declaration in JAVA format
431      */
432     def protected generateClassDeclaration(boolean isInnerClass) '''
433         public«
434         IF (isInnerClass)»«
435             " static final "»«
436         ELSEIF (type.abstract)»«
437             " abstract "»«
438         ELSE»«
439             " "»«
440         ENDIF»class «type.name»«
441         IF (genTO.superType !== null)»«
442             " extends "»«genTO.superType.importedName»«
443         ENDIF»
444         «IF (!type.implements.empty)»«
445             " implements "»«
446             FOR type : type.implements SEPARATOR ", "»«
447                 type.importedName»«
448             ENDFOR»«
449         ENDIF
450     »'''
451
452     /**
453      * Template method which generates JAVA enum type.
454      *
455      * @return string with inner enum source code in JAVA format
456      */
457     def protected enumDeclarations() '''
458         «IF !enums.empty»
459             «FOR e : enums SEPARATOR "\n"»
460                 «new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
461             «ENDFOR»
462         «ENDIF»
463     '''
464
465     def protected suidDeclaration() '''
466         «IF genTO.SUID !== null»
467             private static final long serialVersionUID = «genTO.SUID.value»L;
468         «ENDIF»
469     '''
470
471     def protected annotationDeclaration() '''
472         «IF genTO.getAnnotations !== null»
473             «FOR e : genTO.getAnnotations»
474                 @«e.getName»
475             «ENDFOR»
476         «ENDIF»
477     '''
478
479     /**
480      * Template method which generates JAVA constants.
481      *
482      * @return string with constants in JAVA format
483      */
484     def protected constantsDeclarations() '''
485         «IF !consts.empty»
486             «FOR c : consts»
487                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
488                     «val cValue = c.value as Map<String, String>»
489                     public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
490                     FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
491                     «IF cValue.size == 1»
492                         private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST» = «Pattern.importedName».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
493                         private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.iterator.next.escapeJava»";
494                     «ELSE»
495                         private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST» = «CodeHelpers.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
496                         private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
497                         FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
498                     «ENDIF»
499                 «ELSE»
500                     «emitConstant(c)»
501                 «ENDIF»
502             «ENDFOR»
503         «ENDIF»
504     '''
505
506     /**
507      * Template method which generates JAVA class attributes.
508      *
509      * @return string with the class attributes in JAVA format
510      */
511     def protected generateFields() '''
512         «IF !properties.empty»
513             «FOR f : properties»
514                 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
515             «ENDFOR»
516         «ENDIF»
517     '''
518
519     protected def isReadOnly(GeneratedProperty field) {
520         return field.readOnly
521     }
522
523     /**
524      * Template method which generates the method <code>hashCode()</code>.
525      *
526      * @return string with the <code>hashCode()</code> method definition in JAVA format
527      */
528     def protected generateHashCode() {
529         val size = genTO.hashCodeIdentifiers.size
530         if (size == 0) {
531             return ""
532         }
533         return '''
534             @«Override.importedName»
535             public int hashCode() {
536                 «IF size != 1»
537                     «hashCodeResult(genTO.hashCodeIdentifiers)»
538                     return result;
539                 «ELSE»
540                     return «CodeHelpers.importedName».wrapperHashCode(«genTO.hashCodeIdentifiers.get(0).fieldName»);
541                 «ENDIF»
542             }
543         '''
544     }
545
546     /**
547      * Template method which generates the method <code>equals()</code>.
548      *
549      * @return string with the <code>equals()</code> method definition in JAVA format
550      */
551     def private generateEquals() '''
552         «IF !genTO.equalsIdentifiers.empty»
553             @«Override.importedName»
554             public final boolean equals(java.lang.Object obj) {
555                 if (this == obj) {
556                     return true;
557                 }
558                 if (!(obj instanceof «type.name»)) {
559                     return false;
560                 }
561                 final «type.name» other = («type.name») obj;
562                 «FOR property : genTO.equalsIdentifiers»
563                     «val fieldName = property.fieldName»
564                     if (!«property.importedUtilClass».equals(«fieldName», other.«fieldName»)) {
565                         return false;
566                     }
567                 «ENDFOR»
568                 return true;
569             }
570         «ENDIF»
571     '''
572
573     def GeneratedProperty getPropByName(String name) {
574         for (GeneratedProperty prop : allProperties) {
575             if (prop.name.equals(name)) {
576                 return prop;
577             }
578         }
579         return null;
580     }
581
582 }