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