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