Revert Merge "Bug 2366: new parser - Types & TypeDefs"
[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             /**
169              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
170              */
171             @Deprecated
172             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
173                 return «varName»;
174             }
175         «ENDIF»
176     '''
177
178     def private generateRangeMethod(String methodName, String varName) '''
179         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
180             «val returnType = allProperties.iterator.next.returnType»
181             /**
182              * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
183              */
184             @Deprecated
185             public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
186                 return «varName»;
187             }
188         «ENDIF»
189     '''
190
191     /**
192      * Template method which generates inner classes inside this interface.
193      *
194      * @return string with the source code for inner classes in JAVA format
195      */
196     def protected innerClassesDeclarations() '''
197         «IF !enclosedGeneratedTypes.empty»
198             «FOR innerClass : enclosedGeneratedTypes SEPARATOR "\n"»
199                 «IF (innerClass instanceof GeneratedTransferObject)»
200                     «val classTemplate = new ClassTemplate(innerClass)»
201                     «classTemplate.generateAsInnerClass»
202
203                 «ENDIF»
204             «ENDFOR»
205         «ENDIF»
206     '''
207
208     def protected constructors() '''
209         «IF genTO.unionType»
210             «genUnionConstructor»
211         «ELSE»
212             «allValuesConstructor»
213         «ENDIF»
214         «IF !allProperties.empty»
215             «copyConstructor»
216         «ENDIF»
217         «IF properties.empty && !parentProperties.empty »
218             «parentConstructor»
219         «ENDIF»
220     '''
221
222     def private generateConstraints() '''
223         static {
224             «IF !restrictions.rangeConstraints.nullOrEmpty»
225             «generateRangeConstraints»
226             «ENDIF»
227             «IF !restrictions.lengthConstraints.nullOrEmpty»
228             «generateLengthConstraints»
229             «ENDIF»
230         }
231     '''
232
233     private def generateRangeConstraints() '''
234         «IF !allProperties.nullOrEmpty»
235             «val returnType = allProperties.iterator.next.returnType»
236             «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
237                 «rangeBody(restrictions, BigDecimal, genTO.importedName, "_range")»
238             «ELSE»
239                 «rangeBody(restrictions, BigInteger, genTO.importedName, "_range")»
240             «ENDIF»
241         «ENDIF»
242     '''
243
244     private def rangeBody(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.rangeConstraints»
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 lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
253         «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
254         «FOR r : restrictions.lengthConstraints»
255             builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
256         «ENDFOR»
257         «varName» = builder.build();
258     '''
259
260     private def generateLengthConstraints() '''
261         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
262             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
263             «IF numberClass.equals(typeof(BigDecimal))»
264                 «lengthBody(restrictions, numberClass, genTO.importedName, "_length")»
265             «ELSE»
266                 «lengthBody(restrictions, typeof(BigInteger), genTO.importedName, "_length")»
267             «ENDIF»
268         «ENDIF»
269     '''
270
271     def protected allValuesConstructor() '''
272     «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
273         @«ConstructorProperties.importedName»("value")
274     «ENDIF»
275     public «type.name»(«allProperties.asArgumentsDeclaration») {
276         «IF false == parentProperties.empty»
277             super(«parentProperties.asArguments»);
278         «ENDIF»
279         «FOR p : allProperties»
280             «generateRestrictions(type, p.fieldName.toString, p.returnType)»
281         «ENDFOR»
282
283         «/*
284          * If we have patterns, we need to apply them to the value field. This is a sad
285          * consequence of how this code is structured.
286          */
287         IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
288
289         «Preconditions.importedName».checkNotNull(_value, "Supplied value may not be null");
290
291             «FOR c : consts»
292                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME && c.value instanceof List<?>»
293             boolean valid = false;
294             for (Pattern p : patterns) {
295                 if (p.matcher(_value).matches()) {
296                     valid = true;
297                     break;
298                 }
299             }
300
301             «Preconditions.importedName».checkArgument(valid, "Supplied value \"%s\" does not match any of the permitted patterns %s", _value, «TypeConstants.PATTERN_CONSTANT_NAME»);
302                 «ENDIF»
303             «ENDFOR»
304         «ENDIF»
305
306         «FOR p : properties»
307             «IF p.returnType.importedName.contains("[]")»
308             this.«p.fieldName» = «p.fieldName» == null ? null : «p.fieldName».clone();
309             «ELSE»
310             this.«p.fieldName» = «p.fieldName»;
311             «ENDIF»
312         «ENDFOR»
313     }
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
327     def protected genConstructor(GeneratedProperty property, GeneratedProperty... other) '''
328     public «type.name»(«property.returnType.importedName + " " + property.name») {
329         «IF false == parentProperties.empty»
330             super(«parentProperties.asArguments»);
331         «ENDIF»
332
333         «generateRestrictions(type, property.fieldName.toString, property.returnType)»
334
335         this.«property.fieldName» = «property.name»;
336         «FOR p : other»
337             this.«p.fieldName» = null;
338         «ENDFOR»
339     }
340     '''
341
342     def protected copyConstructor() '''
343     /**
344      * Creates a copy from Source Object.
345      *
346      * @param source Source object
347      */
348     public «type.name»(«type.name» source) {
349         «IF false == parentProperties.empty»
350             super(source);
351         «ENDIF»
352         «FOR p : properties»
353             this.«p.fieldName» = source.«p.fieldName»;
354         «ENDFOR»
355     }
356     '''
357
358     def protected parentConstructor() '''
359     /**
360      * Creates a new instance from «genTO.superType.importedName»
361      *
362      * @param source Source object
363      */
364     public «type.name»(«genTO.superType.importedName» source) {
365             super(source);
366     }
367     '''
368
369     def protected defaultInstance() '''
370         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
371             «val prop = allProperties.get(0)»
372             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
373             public static «genTO.name» getDefaultInstance(String defaultValue) {
374                 «IF "byte[]".equals(prop.returnType.name)»
375                     «BaseEncoding.importedName» baseEncoding = «BaseEncoding.importedName».base64();
376                     return new «genTO.name»(baseEncoding.decode(defaultValue));
377                 «ELSEIF "java.lang.String".equals(prop.returnType.fullyQualifiedName)»
378                     return new «genTO.name»(defaultValue);
379                 «ELSEIF allProperties.size > 1»
380                     «bitsArgs»
381                 «ELSEIF "java.lang.Boolean".equals(prop.returnType.fullyQualifiedName)»
382                     return new «genTO.name»(Boolean.valueOf(defaultValue));
383                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
384                     return new «genTO.name»(Byte.valueOf(defaultValue));
385                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
386                     return new «genTO.name»(Short.valueOf(defaultValue));
387                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
388                     return new «genTO.name»(Integer.valueOf(defaultValue));
389                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
390                     return new «genTO.name»(Long.valueOf(defaultValue));
391                 «ELSE»
392                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
393                 «ENDIF»
394             }
395             «ENDIF»
396         «ENDIF»
397     '''
398
399     def protected bitsArgs() '''
400         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
401         if (!properties.contains(defaultValue)) {
402             throw new «IllegalArgumentException.importedName»("invalid default parameter");
403         }
404         int i = 0;
405         return new «genTO.name»(
406         «FOR prop : allProperties SEPARATOR ","»
407             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
408         «ENDFOR»
409         );
410     '''
411
412     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
413         «FOR prop : properties SEPARATOR ","»
414             "«prop.name»"
415         «ENDFOR»
416     '''
417
418     /**
419      * Template method which generates JAVA class declaration.
420      *
421      * @param isInnerClass boolean value which specify if generated class is|isn't inner
422      * @return string with class declaration in JAVA format
423      */
424     def protected generateClassDeclaration(boolean isInnerClass) '''
425         public«
426         IF (isInnerClass)»«
427             " static final "»«
428         ELSEIF (type.abstract)»«
429             " abstract "»«
430         ELSE»«
431             " "»«
432         ENDIF»class «type.name»«
433         IF (genTO.superType != null)»«
434             " extends "»«genTO.superType.importedName»«
435         ENDIF»
436         «IF (!type.implements.empty)»«
437             " implements "»«
438             FOR type : type.implements SEPARATOR ", "»«
439                 type.importedName»«
440             ENDFOR»«
441         ENDIF
442     »'''
443
444     /**
445      * Template method which generates JAVA enum type.
446      *
447      * @return string with inner enum source code in JAVA format
448      */
449     def protected enumDeclarations() '''
450         «IF !enums.empty»
451             «FOR e : enums SEPARATOR "\n"»
452                 «val enumTemplate = new EnumTemplate(e)»
453                 «enumTemplate.generateAsInnerClass»
454             «ENDFOR»
455         «ENDIF»
456     '''
457
458     def protected suidDeclaration() '''
459         «IF genTO.SUID != null»
460             private static final long serialVersionUID = «genTO.SUID.value»L;
461         «ENDIF»
462     '''
463
464     /**
465      * Template method which generates JAVA constants.
466      *
467      * @return string with constants in JAVA format
468      */
469     def protected constantsDeclarations() '''
470         «IF !consts.empty»
471             «FOR c : consts»
472                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
473                     «val cValue = c.value»
474                     «IF cValue instanceof List<?>»
475                         private static final «List.importedName»<«Pattern.importedName»> «Constants.MEMBER_PATTERN_LIST»;
476                         public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
477                         FOR v : cValue SEPARATOR ", "»«
478                             IF v instanceof String»"«
479                                 v»"«
480                             ENDIF»«
481                         ENDFOR»);
482
483                         «generateStaticInicializationBlock»
484                     «ENDIF»
485                 «ELSE»
486                     public static final «c.type.importedName» «c.name» = «c.value»;
487                 «ENDIF»
488             «ENDFOR»
489         «ENDIF»
490     '''
491
492     /**
493      * Template method which generates JAVA static initialization block.
494      *
495      * @return string with static initialization block in JAVA format
496      */
497     def protected generateStaticInicializationBlock() '''
498         static {
499             final «List.importedName»<«Pattern.importedName»> l = new «ArrayList.importedName»<«Pattern.importedName»>();
500             for (String regEx : «TypeConstants.PATTERN_CONSTANT_NAME») {
501                 l.add(Pattern.compile(regEx));
502             }
503
504             «Constants.MEMBER_PATTERN_LIST» = «ImmutableList.importedName».copyOf(l);
505         }
506     '''
507
508     /**
509      * Template method which generates JAVA class attributes.
510      *
511      * @return string with the class attributes in JAVA format
512      */
513     def protected generateFields() '''
514         «IF restrictions != null»
515             «val prop = getPropByName("value")»
516             «IF prop != null»
517                 «IF !(restrictions.lengthConstraints.empty)»
518                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length;
519                 «ENDIF»
520                 «IF !(restrictions.rangeConstraints.empty)»
521                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range;
522                 «ENDIF»
523             «ENDIF»
524         «ENDIF»
525         «IF !properties.empty»
526             «FOR f : properties»
527                 private«IF f.readOnly» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
528             «ENDFOR»
529         «ENDIF»
530     '''
531
532     /**
533      * Template method which generates the method <code>hashCode()</code>.
534      *
535      * @return string with the <code>hashCode()</code> method definition in JAVA format
536      */
537     def protected generateHashCode() '''
538         «IF !genTO.hashCodeIdentifiers.empty»
539             @Override
540             public int hashCode() {
541                 final int prime = 31;
542                 int result = 1;
543                 «FOR property : genTO.hashCodeIdentifiers»
544                     «IF property.returnType.name.contains("[")»
545                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
546                     «ELSE»
547                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
548                     «ENDIF»
549                 «ENDFOR»
550                 return result;
551             }
552         «ENDIF»
553     '''
554
555     /**
556      * Template method which generates the method <code>equals()</code>.
557      *
558      * @return string with the <code>equals()</code> method definition in JAVA format
559      */
560     def protected generateEquals() '''
561         «IF !genTO.equalsIdentifiers.empty»
562             @Override
563             public boolean equals(java.lang.Object obj) {
564                 if (this == obj) {
565                     return true;
566                 }
567                 if (obj == null) {
568                     return false;
569                 }
570                 if (getClass() != obj.getClass()) {
571                     return false;
572                 }
573                 «type.name» other = («type.name») obj;
574                 «FOR property : genTO.equalsIdentifiers»
575                     «val fieldName = property.fieldName»
576                     if («fieldName» == null) {
577                         if (other.«fieldName» != null) {
578                             return false;
579                         }
580                     «IF property.returnType.name.contains("[")»
581                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
582                     «ELSE»
583                     } else if(!«fieldName».equals(other.«fieldName»)) {
584                     «ENDIF»
585                         return false;
586                     }
587                 «ENDFOR»
588                 return true;
589             }
590         «ENDIF»
591     '''
592
593     def GeneratedProperty getPropByName(String name) {
594         for (GeneratedProperty prop : allProperties) {
595             if (prop.name.equals(name)) {
596                 return prop;
597             }
598         }
599         return null;
600     }
601
602 }