Bug 1434 - Make arrays non-mutable
[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             «IF p.returnType.importedName.contains("[]")»
300             this.«p.fieldName» = Arrays.copyOf(«p.fieldName», «p.fieldName».length);
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                         «val cValues = cValue as List<?>»
468                         private static final «List.importedName»<«Pattern.importedName»> «Constants.MEMBER_PATTERN_LIST»;
469                         public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
470                         FOR v : cValues SEPARATOR ", "»«
471                             IF v instanceof String»"«
472                                 v as String»"«
473                             ENDIF»«
474                         ENDFOR»);
475
476                         «generateStaticInicializationBlock»
477                     «ENDIF»
478                 «ELSE»
479                     public static final «c.type.importedName» «c.name» = «c.value»;
480                 «ENDIF»
481             «ENDFOR»
482         «ENDIF»
483     '''
484
485     /**
486      * Template method which generates JAVA static initialization block.
487      *
488      * @return string with static initialization block in JAVA format
489      */
490     def protected generateStaticInicializationBlock() '''
491         static {
492             final «List.importedName»<«Pattern.importedName»> l = new «ArrayList.importedName»<«Pattern.importedName»>();
493             for (String regEx : «TypeConstants.PATTERN_CONSTANT_NAME») {
494                 l.add(Pattern.compile(regEx));
495             }
496
497             «Constants.MEMBER_PATTERN_LIST» = «ImmutableList.importedName».copyOf(l);
498         }
499     '''
500
501     /**
502      * Template method which generates JAVA class attributes.
503      *
504      * @return string with the class attributes in JAVA format
505      */
506     def protected generateFields() '''
507         «IF restrictions != null»
508             «val prop = getPropByName("value")»
509             «IF prop != null»
510                 «IF !(restrictions.lengthConstraints.empty)»
511                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length;
512                 «ENDIF»
513                 «IF !(restrictions.rangeConstraints.empty)»
514                     private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range;
515                 «ENDIF»
516             «ENDIF»
517         «ENDIF»
518         «IF !properties.empty»
519             «FOR f : properties»
520                 private«IF f.readOnly» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
521             «ENDFOR»
522         «ENDIF»
523     '''
524
525     /**
526      * Template method which generates the method <code>hashCode()</code>.
527      *
528      * @return string with the <code>hashCode()</code> method definition in JAVA format
529      */
530     def protected generateHashCode() '''
531         «IF !genTO.hashCodeIdentifiers.empty»
532             @Override
533             public int hashCode() {
534                 final int prime = 31;
535                 int result = 1;
536                 «FOR property : genTO.hashCodeIdentifiers»
537                     «IF property.returnType.name.contains("[")»
538                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
539                     «ELSE»
540                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
541                     «ENDIF»
542                 «ENDFOR»
543                 return result;
544             }
545         «ENDIF»
546     '''
547
548     /**
549      * Template method which generates the method <code>equals()</code>.
550      *
551      * @return string with the <code>equals()</code> method definition in JAVA format
552      */
553     def protected generateEquals() '''
554         «IF !genTO.equalsIdentifiers.empty»
555             @Override
556             public boolean equals(java.lang.Object obj) {
557                 if (this == obj) {
558                     return true;
559                 }
560                 if (obj == null) {
561                     return false;
562                 }
563                 if (getClass() != obj.getClass()) {
564                     return false;
565                 }
566                 «type.name» other = («type.name») obj;
567                 «FOR property : genTO.equalsIdentifiers»
568                     «val fieldName = property.fieldName»
569                     if («fieldName» == null) {
570                         if (other.«fieldName» != null) {
571                             return false;
572                         }
573                     «IF property.returnType.name.contains("[")»
574                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
575                     «ELSE»
576                     } else if(!«fieldName».equals(other.«fieldName»)) {
577                     «ENDIF»
578                         return false;
579                     }
580                 «ENDFOR»
581                 return true;
582             }
583         «ENDIF»
584     '''
585
586     def GeneratedProperty getPropByName(String name) {
587         for (GeneratedProperty prop : allProperties) {
588             if (prop.name.equals(name)) {
589                 return prop;
590             }
591         }
592         return null;
593     }
594
595 }