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