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