350d579dcd3cfb414002c37d7ef7146e35022225
[mdsal.git] / code-generator / binding-java-api-generator / src / main / java / org / opendaylight / yangtools / sal / 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.yangtools.sal.java.api.generator
9
10 import com.google.common.collect.ImmutableList
11 import com.google.common.collect.Lists
12 import com.google.common.collect.Range
13 import com.google.common.io.BaseEncoding
14 import java.beans.ConstructorProperties
15 import java.math.BigDecimal
16 import java.math.BigInteger
17 import java.util.ArrayList
18 import java.util.Arrays
19 import java.util.Collections
20 import java.util.List
21 import java.util.regex.Pattern
22 import org.opendaylight.yangtools.binding.generator.util.TypeConstants
23 import org.opendaylight.yangtools.sal.binding.model.api.Constant
24 import org.opendaylight.yangtools.sal.binding.model.api.Enumeration
25 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
26 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
28 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
29 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
30 import com.google.common.base.Preconditions
31 import org.opendaylight.yangtools.sal.binding.model.api.Type
32 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
33
34 /**
35  * Template for generating JAVA class.
36  */
37 class ClassTemplate extends BaseTemplate {
38
39     protected val List<GeneratedProperty> properties
40     protected val List<GeneratedProperty> finalProperties
41     protected val List<GeneratedProperty> parentProperties
42     protected val Iterable<GeneratedProperty> allProperties;
43     protected val Restrictions restrictions
44
45     /**
46      * List of enumeration which are generated as JAVA enum type.
47      */
48     protected val List<Enumeration> enums
49
50     /**
51      * List of constant instances which are generated as JAVA public static final attributes.
52      */
53     protected val List<Constant> consts
54
55     /**
56      * List of generated types which are enclosed inside <code>genType</code>
57      */
58     protected val List<GeneratedType> enclosedGeneratedTypes;
59
60     protected val GeneratedTransferObject genTO;
61
62     private val AbstractRangeGenerator<?> rangeGenerator
63
64     /**
65      * Creates instance of this class with concrete <code>genType</code>.
66      *
67      * @param genType generated transfer object which will be transformed to JAVA class source code
68      */
69     new(GeneratedTransferObject genType) {
70         super(genType)
71         this.genTO = genType
72         this.properties = genType.properties
73         this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
74         this.parentProperties = GeneratorUtil.getPropertiesOfAllParents(genTO)
75         this.restrictions = genType.restrictions
76
77         var List<GeneratedProperty> sorted = new ArrayList<GeneratedProperty>();
78         sorted.addAll(properties);
79         sorted.addAll(parentProperties);
80         Collections.sort(sorted, [p1, p2|
81             p1.name.compareTo(p2.name)
82         ]);
83
84         this.allProperties = sorted
85         this.enums = genType.enumerations
86         this.consts = genType.constantDefinitions
87         this.enclosedGeneratedTypes = genType.enclosedTypes
88
89         if (restrictions != null && !restrictions.rangeConstraints.nullOrEmpty) {
90             rangeGenerator = AbstractRangeGenerator.forType(findProperty(genType, "value").returnType)
91             Preconditions.checkNotNull(rangeGenerator)
92         } else {
93             rangeGenerator = null
94         }
95     }
96
97     /**
98      * Generates JAVA class source code (class body only).
99      *
100      * @return string with JAVA class body source code
101      */
102     def CharSequence generateAsInnerClass() {
103         return generateBody(true)
104     }
105
106     override protected body() {
107         generateBody(false);
108     }
109
110     /**
111      * Template method which generates class body.
112      *
113      * @param isInnerClass boolean value which specify if generated class is|isn't inner
114      * @return string with class source code in JAVA format
115      */
116     def protected generateBody(boolean isInnerClass) '''
117         «wrapToDocumentation(formatDataForJavaDoc(type))»
118         «generateClassDeclaration(isInnerClass)» {
119             «suidDeclaration»
120             «innerClassesDeclarations»
121             «enumDeclarations»
122             «constantsDeclarations»
123             «generateFields»
124
125             «IF restrictions != null && (!restrictions.rangeConstraints.nullOrEmpty ||
126                 !restrictions.lengthConstraints.nullOrEmpty)»
127             «generateConstraints»
128
129             «IF !restrictions.rangeConstraints.nullOrEmpty»
130             «rangeGenerator.generateRangeChecker("_value", restrictions.rangeConstraints)»
131             «ENDIF»
132             «ENDIF»
133
134             «constructors»
135
136             «defaultInstance»
137
138             «FOR field : properties SEPARATOR "\n"»
139                 «field.getterMethod»
140                 «IF !field.readOnly»
141                     «field.setterMethod»
142                 «ENDIF»
143             «ENDFOR»
144
145             «IF (genTO.isTypedef() && genTO.getBaseType instanceof BitsTypeDefinition)»
146                 «generateGetValueForBitsTypeDef»
147             «ENDIF»
148
149             «generateHashCode»
150
151             «generateEquals»
152
153             «generateToString(genTO.toStringIdentifiers)»
154
155             «generateLengthMethod("length", "_length")»
156
157             «generateRangeMethod("range", "_range")»
158
159         }
160
161     '''
162
163     /**
164      * Template method which generates the method <code>getValue()</code> for typedef,
165      * which base type is BitsDefinition.
166      *
167      * @return string with the <code>getValue()</code> method definition in JAVA format
168      */
169     def protected generateGetValueForBitsTypeDef() '''
170
171         public boolean[] getValue() {
172             return new boolean[]{
173             «FOR property: genTO.properties SEPARATOR ','»
174                  «property.fieldName»
175             «ENDFOR»
176             };
177         }
178     '''
179
180     def private generateLengthMethod(String methodName, String varName) '''
181         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
182             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
183             /**
184              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
185              */
186             @Deprecated
187             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
188                 return «varName»;
189             }
190         «ENDIF»
191     '''
192
193     def private generateRangeMethod(String methodName, String varName) '''
194         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
195             «val returnType = allProperties.iterator.next.returnType»
196             /**
197              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
198              */
199             @Deprecated
200             public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
201                 return «varName»;
202             }
203         «ENDIF»
204     '''
205
206     /**
207      * Template method which generates inner classes inside this interface.
208      *
209      * @return string with the source code for inner classes in JAVA format
210      */
211     def protected innerClassesDeclarations() '''
212         «IF !enclosedGeneratedTypes.empty»
213             «FOR innerClass : enclosedGeneratedTypes SEPARATOR "\n"»
214                 «IF (innerClass instanceof GeneratedTransferObject)»
215                     «val classTemplate = new ClassTemplate(innerClass)»
216                     «classTemplate.generateAsInnerClass»
217
218                 «ENDIF»
219             «ENDFOR»
220         «ENDIF»
221     '''
222
223     def protected constructors() '''
224         «IF genTO.unionType»
225             «genUnionConstructor»
226         «ELSE»
227             «allValuesConstructor»
228         «ENDIF»
229         «IF !allProperties.empty»
230             «copyConstructor»
231         «ENDIF»
232         «IF properties.empty && !parentProperties.empty »
233             «parentConstructor»
234         «ENDIF»
235     '''
236
237     def private generateConstraints() '''
238         static {
239             «IF !restrictions.rangeConstraints.nullOrEmpty»
240             «generateRangeConstraints»
241             «ENDIF»
242             «IF !restrictions.lengthConstraints.nullOrEmpty»
243             «generateLengthConstraints»
244             «ENDIF»
245         }
246     '''
247
248     private def generateRangeConstraints() '''
249         «IF !allProperties.nullOrEmpty»
250             «val returnType = allProperties.iterator.next.returnType»
251             «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
252                 «rangeBody(restrictions, BigDecimal, genTO.importedName, "_range")»
253             «ELSE»
254                 «rangeBody(restrictions, BigInteger, genTO.importedName, "_range")»
255             «ENDIF»
256         «ENDIF»
257     '''
258
259     private def rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
260         «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
261         «FOR r : restrictions.rangeConstraints»
262             builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
263         «ENDFOR»
264         «varName» = builder.build();
265     '''
266
267     private def lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
268         «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
269         «FOR r : restrictions.lengthConstraints»
270             builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
271         «ENDFOR»
272         «varName» = builder.build();
273     '''
274
275     private def generateLengthConstraints() '''
276         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
277             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
278             «IF numberClass.equals(typeof(BigDecimal))»
279                 «lengthBody(restrictions, numberClass, genTO.importedName, "_length")»
280             «ELSE»
281                 «lengthBody(restrictions, typeof(BigInteger), genTO.importedName, "_length")»
282             «ENDIF»
283         «ENDIF»
284     '''
285
286     def protected allValuesConstructor() '''
287     «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
288         @«ConstructorProperties.importedName»("value")
289     «ENDIF»
290     public «type.name»(«allProperties.asArgumentsDeclaration») {
291         «IF false == parentProperties.empty»
292             super(«parentProperties.asArguments»);
293         «ENDIF»
294         «FOR p : allProperties»
295             «generateRestrictions(type, p.fieldName.toString, p.returnType)»
296         «ENDFOR»
297
298         «/*
299          * If we have patterns, we need to apply them to the value field. This is a sad
300          * consequence of how this code is structured.
301          */
302         IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
303
304         «Preconditions.importedName».checkNotNull(_value, "Supplied value may not be null");
305
306             «FOR c : consts»
307                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME && c.value instanceof List<?>»
308             for (Pattern p : patterns) {
309                 «Preconditions.importedName».checkArgument(p.matcher(_value).matches(), "Supplied value \"%s\" does not match any of the permitted patterns %s", _value, «TypeConstants.PATTERN_CONSTANT_NAME»);
310             }
311
312                 «ENDIF»
313             «ENDFOR»
314         «ENDIF»
315
316         «FOR p : properties»
317             «IF p.returnType.importedName.contains("[]")»
318             this.«p.fieldName» = «p.fieldName» == null ? null : «p.fieldName».clone();
319             «ELSE»
320             this.«p.fieldName» = «p.fieldName»;
321             «ENDIF»
322         «ENDFOR»
323     }
324
325     '''
326
327     def protected genUnionConstructor() '''
328     «FOR p : allProperties»
329         «val List<GeneratedProperty> other = new ArrayList(properties)»
330         «IF other.remove(p)»
331             «genConstructor(p, other)»
332         «ENDIF»
333     «ENDFOR»
334
335     '''
336
337     def protected genConstructor(GeneratedProperty property, GeneratedProperty... other) '''
338     public «type.name»(«property.returnType.importedName + " " + property.name») {
339         «IF false == parentProperties.empty»
340             super(«parentProperties.asArguments»);
341         «ENDIF»
342
343         «generateRestrictions(type, property.fieldName.toString, property.returnType)»
344
345         this.«property.fieldName» = «property.name»;
346         «FOR p : other»
347             this.«p.fieldName» = null;
348         «ENDFOR»
349     }
350     '''
351
352     def private generateRestrictions(Type type, String paramName, Type returnType) '''
353         «val restrictions = type.getRestrictions»
354         «IF restrictions !== null»
355             «val boolean isNestedType = !(returnType instanceof ConcreteType)»
356             «IF !restrictions.lengthConstraints.empty»
357                 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
358             «ENDIF»
359             «IF !restrictions.rangeConstraints.empty»
360                 if («paramName» != null) {
361                     «IF isNestedType»
362                         «rangeGenerator.generateRangeCheckerCall(paramName, paramName + ".getValue()")»
363                     «ELSE»
364                         «rangeGenerator.generateRangeCheckerCall(paramName, paramName)»
365                     «ENDIF»
366                 }
367             «ENDIF»
368         «ENDIF»
369     '''
370
371     def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
372         «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
373         if («paramName» != null) {
374             «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
375             boolean isValidLength = false;
376             for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
377                 if (r.contains(_constraint)) {
378                     isValidLength = true;
379                 }
380             }
381             if (!isValidLength) {
382                 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
383             }
384         }
385     '''
386
387     def protected copyConstructor() '''
388     /**
389      * Creates a copy from Source Object.
390      *
391      * @param source Source object
392      */
393     public «type.name»(«type.name» source) {
394         «IF false == parentProperties.empty»
395             super(source);
396         «ENDIF»
397         «FOR p : properties»
398             this.«p.fieldName» = source.«p.fieldName»;
399         «ENDFOR»
400     }
401     '''
402
403     def protected parentConstructor() '''
404     /**
405      * Creates a new instance from «genTO.superType.importedName»
406      *
407      * @param source Source object
408      */
409     public «type.name»(«genTO.superType.importedName» source) {
410             super(source);
411     }
412     '''
413
414     def protected defaultInstance() '''
415         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
416             «val prop = allProperties.get(0)»
417             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
418             public static «genTO.name» getDefaultInstance(String defaultValue) {
419                 «IF "byte[]".equals(prop.returnType.name)»
420                     «BaseEncoding.importedName» baseEncoding = «BaseEncoding.importedName».base64();
421                     return new «genTO.name»(baseEncoding.decode(defaultValue));
422                 «ELSEIF "java.lang.String".equals(prop.returnType.fullyQualifiedName)»
423                     return new «genTO.name»(defaultValue);
424                 «ELSEIF allProperties.size > 1»
425                     «bitsArgs»
426                 «ELSEIF "java.lang.Boolean".equals(prop.returnType.fullyQualifiedName)»
427                     return new «genTO.name»(Boolean.valueOf(defaultValue));
428                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
429                     return new «genTO.name»(Byte.valueOf(defaultValue));
430                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
431                     return new «genTO.name»(Short.valueOf(defaultValue));
432                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
433                     return new «genTO.name»(Integer.valueOf(defaultValue));
434                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
435                     return new «genTO.name»(Long.valueOf(defaultValue));
436                 «ELSE»
437                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
438                 «ENDIF»
439             }
440             «ENDIF»
441         «ENDIF»
442     '''
443
444     def protected bitsArgs() '''
445         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
446         if (!properties.contains(defaultValue)) {
447             throw new «IllegalArgumentException.importedName»("invalid default parameter");
448         }
449         int i = 0;
450         return new «genTO.name»(
451         «FOR prop : allProperties SEPARATOR ","»
452             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
453         «ENDFOR»
454         );
455     '''
456
457     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
458         «FOR prop : properties SEPARATOR ","»
459             "«prop.name»"
460         «ENDFOR»
461     '''
462
463     /**
464      * Template method which generates JAVA class declaration.
465      *
466      * @param isInnerClass boolean value which specify if generated class is|isn't inner
467      * @return string with class declaration in JAVA format
468      */
469     def protected generateClassDeclaration(boolean isInnerClass) '''
470         public«
471         IF (isInnerClass)»«
472             " static final "»«
473         ELSEIF (type.abstract)»«
474             " abstract "»«
475         ELSE»«
476             " "»«
477         ENDIF»class «type.name»«
478         IF (genTO.superType != null)»«
479             " extends "»«genTO.superType.importedName»«
480         ENDIF»
481         «IF (!type.implements.empty)»«
482             " implements "»«
483             FOR type : type.implements SEPARATOR ", "»«
484                 type.importedName»«
485             ENDFOR»«
486         ENDIF
487     »'''
488
489     /**
490      * Template method which generates JAVA enum type.
491      *
492      * @return string with inner enum source code in JAVA format
493      */
494     def protected enumDeclarations() '''
495         «IF !enums.empty»
496             «FOR e : enums SEPARATOR "\n"»
497                 «val enumTemplate = new EnumTemplate(e)»
498                 «enumTemplate.generateAsInnerClass»
499             «ENDFOR»
500         «ENDIF»
501     '''
502
503     def protected suidDeclaration() '''
504         «IF genTO.SUID != null»
505             private static final long serialVersionUID = «genTO.SUID.value»L;
506         «ENDIF»
507     '''
508
509     /**
510      * Template method which generates JAVA constants.
511      *
512      * @return string with constants in JAVA format
513      */
514     def protected constantsDeclarations() '''
515         «IF !consts.empty»
516             «FOR c : consts»
517                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
518                     «val cValue = c.value»
519                     «IF cValue instanceof List<?>»
520                         private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»;
521                         public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
522                         FOR v : cValue SEPARATOR ", "»«
523                             IF v instanceof String»"«
524                                 v»"«
525                             ENDIF»«
526                         ENDFOR»);
527
528                         «generateStaticInicializationBlock»
529                     «ENDIF»
530                 «ELSE»
531                     public static final «c.type.importedName» «c.name» = «c.value»;
532                 «ENDIF»
533             «ENDFOR»
534         «ENDIF»
535     '''
536
537     /**
538      * Template method which generates JAVA static initialization block.
539      *
540      * @return string with static initialization block in JAVA format
541      */
542     def protected generateStaticInicializationBlock() '''
543         static {
544             final «Pattern.importedName» a[] = new «Pattern.importedName»[«TypeConstants.PATTERN_CONSTANT_NAME».size()];
545             int i = 0;
546             for (String regEx : «TypeConstants.PATTERN_CONSTANT_NAME») {
547                 a[i++] = Pattern.compile(regEx);
548             }
549
550             «Constants.MEMBER_PATTERN_LIST» = a;
551         }
552     '''
553
554     /**
555      * Template method which generates JAVA class attributes.
556      *
557      * @return string with the class attributes in JAVA format
558      */
559     def protected generateFields() '''
560         «IF restrictions != null»
561             «val prop = getPropByName("value")»
562             «IF prop != null»
563                 «IF !(restrictions.lengthConstraints.empty)»
564                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length;
565                 «ENDIF»
566                 «IF !(restrictions.rangeConstraints.empty)»
567                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range;
568                 «ENDIF»
569             «ENDIF»
570         «ENDIF»
571         «IF !properties.empty»
572             «FOR f : properties»
573                 private«IF f.readOnly» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
574             «ENDFOR»
575         «ENDIF»
576     '''
577
578     /**
579      * Template method which generates the method <code>hashCode()</code>.
580      *
581      * @return string with the <code>hashCode()</code> method definition in JAVA format
582      */
583     def protected generateHashCode() '''
584         «IF !genTO.hashCodeIdentifiers.empty»
585             @Override
586             public int hashCode() {
587                 final int prime = 31;
588                 int result = 1;
589                 «FOR property : genTO.hashCodeIdentifiers»
590                     «IF property.returnType.name.contains("[")»
591                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
592                     «ELSE»
593                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
594                     «ENDIF»
595                 «ENDFOR»
596                 return result;
597             }
598         «ENDIF»
599     '''
600
601     /**
602      * Template method which generates the method <code>equals()</code>.
603      *
604      * @return string with the <code>equals()</code> method definition in JAVA format
605      */
606     def protected generateEquals() '''
607         «IF !genTO.equalsIdentifiers.empty»
608             @Override
609             public boolean equals(java.lang.Object obj) {
610                 if (this == obj) {
611                     return true;
612                 }
613                 if (obj == null) {
614                     return false;
615                 }
616                 if (getClass() != obj.getClass()) {
617                     return false;
618                 }
619                 «type.name» other = («type.name») obj;
620                 «FOR property : genTO.equalsIdentifiers»
621                     «val fieldName = property.fieldName»
622                     if («fieldName» == null) {
623                         if (other.«fieldName» != null) {
624                             return false;
625                         }
626                     «IF property.returnType.name.contains("[")»
627                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
628                     «ELSE»
629                     } else if(!«fieldName».equals(other.«fieldName»)) {
630                     «ENDIF»
631                         return false;
632                     }
633                 «ENDFOR»
634                 return true;
635             }
636         «ENDIF»
637     '''
638
639     def GeneratedProperty getPropByName(String name) {
640         for (GeneratedProperty prop : allProperties) {
641             if (prop.name.equals(name)) {
642                 return prop;
643             }
644         }
645         return null;
646     }
647
648 }