Merge "DataTreeCandidateNode.getChildNodes() should return a Collection"
[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 as GeneratedTransferObject)»
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             this.«p.fieldName» = «p.fieldName»;
300         «ENDFOR»
301     }
302
303     '''
304
305     def protected genUnionConstructor() '''
306     «FOR p : allProperties»
307         «val List<GeneratedProperty> other = new ArrayList(properties)»
308         «IF other.remove(p)»
309             «genConstructor(p, other)»
310         «ENDIF»
311     «ENDFOR»
312
313     '''
314
315     def protected genConstructor(GeneratedProperty property, GeneratedProperty... other) '''
316     public «type.name»(«property.returnType.importedName + " " + property.name») {
317         «IF false == parentProperties.empty»
318             super(«parentProperties.asArguments»);
319         «ENDIF»
320
321         «generateRestrictions(type, property.fieldName.toString, property.returnType)»
322
323         this.«property.fieldName» = «property.name»;
324         «FOR p : other»
325             this.«p.fieldName» = null;
326         «ENDFOR»
327     }
328     '''
329
330     def protected copyConstructor() '''
331     /**
332      * Creates a copy from Source Object.
333      *
334      * @param source Source object
335      */
336     public «type.name»(«type.name» source) {
337         «IF false == parentProperties.empty»
338             super(source);
339         «ENDIF»
340         «FOR p : properties»
341             this.«p.fieldName» = source.«p.fieldName»;
342         «ENDFOR»
343     }
344     '''
345
346     def protected parentConstructor() '''
347     /**
348      * Creates a new instance from «genTO.superType.importedName»
349      *
350      * @param source Source object
351      */
352     public «type.name»(«genTO.superType.importedName» source) {
353             super(source);
354     }
355     '''
356
357     def protected defaultInstance() '''
358         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
359             «val prop = allProperties.get(0)»
360             «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
361             public static «genTO.name» getDefaultInstance(String defaultValue) {
362                 «IF "byte[]".equals(prop.returnType.name)»
363                     «BaseEncoding.importedName» baseEncoding = «BaseEncoding.importedName».base64();
364                     return new «genTO.name»(baseEncoding.decode(defaultValue));
365                 «ELSEIF "java.lang.String".equals(prop.returnType.fullyQualifiedName)»
366                     return new «genTO.name»(defaultValue);
367                 «ELSEIF allProperties.size > 1»
368                     «bitsArgs»
369                 «ELSEIF "java.lang.Boolean".equals(prop.returnType.fullyQualifiedName)»
370                     return new «genTO.name»(Boolean.valueOf(defaultValue));
371                 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
372                     return new «genTO.name»(Byte.valueOf(defaultValue));
373                 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
374                     return new «genTO.name»(Short.valueOf(defaultValue));
375                 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
376                     return new «genTO.name»(Integer.valueOf(defaultValue));
377                 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
378                     return new «genTO.name»(Long.valueOf(defaultValue));
379                 «ELSE»
380                     return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
381                 «ENDIF»
382             }
383             «ENDIF»
384         «ENDIF»
385     '''
386
387     def protected bitsArgs() '''
388         «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
389         if (!properties.contains(defaultValue)) {
390             throw new «IllegalArgumentException.importedName»("invalid default parameter");
391         }
392         int i = 0;
393         return new «genTO.name»(
394         «FOR prop : allProperties SEPARATOR ","»
395             properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
396         «ENDFOR»
397         );
398     '''
399
400     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
401         «FOR prop : properties SEPARATOR ","»
402             "«prop.name»"
403         «ENDFOR»
404     '''
405
406     /**
407      * Template method which generates JAVA class declaration.
408      *
409      * @param isInnerClass boolean value which specify if generated class is|isn't inner
410      * @return string with class declaration in JAVA format
411      */
412     def protected generateClassDeclaration(boolean isInnerClass) '''
413         public«
414         IF (isInnerClass)»«
415             " static final "»«
416         ELSEIF (type.abstract)»«
417             " abstract "»«
418         ELSE»«
419             " "»«
420         ENDIF»class «type.name»«
421         IF (genTO.superType != null)»«
422             " extends "»«genTO.superType.importedName»«
423         ENDIF»
424         «IF (!type.implements.empty)»«
425             " implements "»«
426             FOR type : type.implements SEPARATOR ", "»«
427                 type.importedName»«
428             ENDFOR»«
429         ENDIF
430     »'''
431
432     /**
433      * Template method which generates JAVA enum type.
434      *
435      * @return string with inner enum source code in JAVA format
436      */
437     def protected enumDeclarations() '''
438         «IF !enums.empty»
439             «FOR e : enums SEPARATOR "\n"»
440                 «val enumTemplate = new EnumTemplate(e)»
441                 «enumTemplate.generateAsInnerClass»
442             «ENDFOR»
443         «ENDIF»
444     '''
445
446     def protected suidDeclaration() '''
447         «IF genTO.SUID != null»
448             private static final long serialVersionUID = «genTO.SUID.value»L;
449         «ENDIF»
450     '''
451
452     /**
453      * Template method which generates JAVA constants.
454      *
455      * @return string with constants in JAVA format
456      */
457     def protected constantsDeclarations() '''
458         «IF !consts.empty»
459             «FOR c : consts»
460                 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
461                     «val cValue = c.value»
462                     «IF cValue instanceof List<?>»
463                         «val cValues = cValue as List<?>»
464                         private static final «List.importedName»<«Pattern.importedName»> «Constants.MEMBER_PATTERN_LIST»;
465                         public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
466                         FOR v : cValues SEPARATOR ", "»«
467                             IF v instanceof String»"«
468                                 v as String»"«
469                             ENDIF»«
470                         ENDFOR»);
471
472                         «generateStaticInicializationBlock»
473                     «ENDIF»
474                 «ELSE»
475                     public static final «c.type.importedName» «c.name» = «c.value»;
476                 «ENDIF»
477             «ENDFOR»
478         «ENDIF»
479     '''
480
481     /**
482      * Template method which generates JAVA static initialization block.
483      *
484      * @return string with static initialization block in JAVA format
485      */
486     def protected generateStaticInicializationBlock() '''
487         static {
488             final «List.importedName»<«Pattern.importedName»> l = new «ArrayList.importedName»<«Pattern.importedName»>();
489             for (String regEx : «TypeConstants.PATTERN_CONSTANT_NAME») {
490                 l.add(Pattern.compile(regEx));
491             }
492
493             «Constants.MEMBER_PATTERN_LIST» = «ImmutableList.importedName».copyOf(l);
494         }
495     '''
496
497     /**
498      * Template method which generates JAVA class attributes.
499      *
500      * @return string with the class attributes in JAVA format
501      */
502     def protected generateFields() '''
503         «IF restrictions != null»
504             «val prop = getPropByName("value")»
505             «IF prop != null»
506                 «IF !(restrictions.lengthConstraints.empty)»
507                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length;
508                 «ENDIF»
509                 «IF !(restrictions.rangeConstraints.empty)»
510                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range;
511                 «ENDIF»
512             «ENDIF»
513         «ENDIF»
514         «IF !properties.empty»
515             «FOR f : properties»
516                 «IF f.readOnly»final«ENDIF» private «f.returnType.importedName» «f.fieldName»;
517             «ENDFOR»
518         «ENDIF»
519     '''
520
521     /**
522      * Template method which generates the method <code>hashCode()</code>.
523      *
524      * @return string with the <code>hashCode()</code> method definition in JAVA format
525      */
526     def protected generateHashCode() '''
527         «IF !genTO.hashCodeIdentifiers.empty»
528             @Override
529             public int hashCode() {
530                 final int prime = 31;
531                 int result = 1;
532                 «FOR property : genTO.hashCodeIdentifiers»
533                     «IF property.returnType.name.contains("[")»
534                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
535                     «ELSE»
536                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
537                     «ENDIF»
538                 «ENDFOR»
539                 return result;
540             }
541         «ENDIF»
542     '''
543
544     /**
545      * Template method which generates the method <code>equals()</code>.
546      *
547      * @return string with the <code>equals()</code> method definition in JAVA format
548      */
549     def protected generateEquals() '''
550         «IF !genTO.equalsIdentifiers.empty»
551             @Override
552             public boolean equals(java.lang.Object obj) {
553                 if (this == obj) {
554                     return true;
555                 }
556                 if (obj == null) {
557                     return false;
558                 }
559                 if (getClass() != obj.getClass()) {
560                     return false;
561                 }
562                 «type.name» other = («type.name») obj;
563                 «FOR property : genTO.equalsIdentifiers»
564                     «val fieldName = property.fieldName»
565                     if («fieldName» == null) {
566                         if (other.«fieldName» != null) {
567                             return false;
568                         }
569                     «IF property.returnType.name.contains("[")»
570                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
571                     «ELSE»
572                     } else if(!«fieldName».equals(other.«fieldName»)) {
573                     «ENDIF»
574                         return false;
575                     }
576                 «ENDFOR»
577                 return true;
578             }
579         «ENDIF»
580     '''
581
582     def GeneratedProperty getPropByName(String name) {
583         for (GeneratedProperty prop : allProperties) {
584             if (prop.name.equals(name)) {
585                 return prop;
586             }
587         }
588         return null;
589     }
590
591 }