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