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