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