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