240a6515d7e3a25daf85e8916e91f6586b95481c
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.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 extension org.apache.commons.text.StringEscapeUtils.escapeJava
11 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
13
14 import com.google.common.collect.ImmutableList
15 import java.util.ArrayList
16 import java.util.Collection
17 import java.util.HashMap
18 import java.util.HashSet
19 import java.util.List
20 import java.util.Map
21 import java.util.Set
22 import java.util.regex.Pattern
23 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
24 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType
26 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
27 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
28 import org.opendaylight.mdsal.binding.model.api.Type
29 import org.opendaylight.mdsal.binding.model.util.TypeConstants
30 import org.opendaylight.mdsal.binding.model.util.Types
31 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
32 import org.opendaylight.yangtools.concepts.Builder
33 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
34 import org.opendaylight.yangtools.yang.binding.CodeHelpers
35 import org.opendaylight.yangtools.yang.binding.DataObject
36
37 /**
38  * Template for generating JAVA builder classes.
39  */
40 class BuilderTemplate extends AbstractBuilderTemplate {
41     /**
42      * Constant used as suffix for builder name.
43      */
44     public static val BUILDER = "Builder";
45
46     /**
47      * Constructs new instance of this class.
48      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
49      */
50     new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
51             Type keyType) {
52         super(genType, targetType, properties, augmentType, keyType)
53     }
54
55     override isLocalInnerClass(JavaTypeName name) {
56         // Builders do not have inner types
57         return false;
58     }
59
60     /**
61      * Template method which generates JAVA class body for builder class and for IMPL class.
62      *
63      * @return string with JAVA source code
64      */
65     override body() '''
66         «wrapToDocumentation(formatDataForJavaDoc(type))»
67         public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
68
69             «generateFields(false)»
70
71             «constantsDeclarations()»
72
73             «IF augmentType !== null»
74                 «generateAugmentField()»
75             «ENDIF»
76
77             «generateConstructorsFromIfcs()»
78
79             public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
80
81             «generateMethodFieldsFrom()»
82
83             «generateGetters(false)»
84             «IF augmentType !== null»
85
86                 «generateAugmentation()»
87             «ENDIF»
88
89             «generateSetters»
90
91             @«Override.importedName»
92             public «targetType.name» build() {
93                 return new «type.enclosedTypes.get(0).importedName»(this);
94             }
95
96             «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
97         }
98     '''
99
100     /**
101      * Generate default constructor and constructor for every implemented interface from uses statements.
102      */
103     def private generateConstructorsFromIfcs() '''
104         public «type.name»() {
105         }
106         «IF (!(targetType instanceof GeneratedTransferObject))»
107             «FOR impl : targetType.implements»
108                 «generateConstructorFromIfc(impl)»
109             «ENDFOR»
110         «ENDIF»
111     '''
112
113     /**
114      * Generate constructor with argument of given type.
115      */
116     def private Object generateConstructorFromIfc(Type impl) '''
117         «IF (impl instanceof GeneratedType)»
118             «IF impl.hasNonDefaultMethods»
119                 public «type.name»(«impl.fullyQualifiedName» arg) {
120                     «printConstructorPropertySetter(impl)»
121                 }
122             «ENDIF»
123             «FOR implTypeImplement : impl.implements»
124                 «generateConstructorFromIfc(implTypeImplement)»
125             «ENDFOR»
126         «ENDIF»
127     '''
128
129     def private Object printConstructorPropertySetter(Type implementedIfc) '''
130         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
131             «val ifc = implementedIfc as GeneratedType»
132             «FOR getter : ifc.nonDefaultMethods»
133                 «IF BindingMapping.isGetterMethodName(getter.name)»
134                     this._«getter.propertyNameFromGetter» = arg.«getter.name»();
135                 «ENDIF»
136             «ENDFOR»
137             «FOR impl : ifc.implements»
138                 «printConstructorPropertySetter(impl)»
139             «ENDFOR»
140         «ENDIF»
141     '''
142
143     /**
144      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
145      */
146     def private generateMethodFieldsFrom() '''
147         «IF (!(targetType instanceof GeneratedTransferObject))»
148             «IF targetType.hasImplementsFromUses»
149                 «val List<Type> done = targetType.getBaseIfcs»
150                 «generateMethodFieldsFromComment(targetType)»
151                 public void fieldsFrom(«DataObject.importedName» arg) {
152                     boolean isValidArg = false;
153                     «FOR impl : targetType.getAllIfcs»
154                         «generateIfCheck(impl, done)»
155                     «ENDFOR»
156                     «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
157                 }
158             «ENDIF»
159         «ENDIF»
160     '''
161
162     def private generateMethodFieldsFromComment(GeneratedType type) '''
163         /**
164          * Set fields from given grouping argument. Valid argument is instance of one of following types:
165          * <ul>
166          «FOR impl : type.getAllIfcs»
167          * <li>«impl.fullyQualifiedName»</li>
168          «ENDFOR»
169          * </ul>
170          *
171          * @param arg grouping object
172          * @throws IllegalArgumentException if given argument is none of valid types
173         */
174     '''
175
176     /**
177      * Method is used to find out if given type implements any interface from uses.
178      */
179     def boolean hasImplementsFromUses(GeneratedType type) {
180         var i = 0
181         for (impl : type.getAllIfcs) {
182             if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
183                 i = i + 1
184             }
185         }
186         return i > 0
187     }
188
189     def private generateIfCheck(Type impl, List<Type> done) '''
190         «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
191             «val implType = impl as GeneratedType»
192             if (arg instanceof «implType.fullyQualifiedName») {
193                 «printPropertySetter(implType)»
194                 isValidArg = true;
195             }
196         «ENDIF»
197     '''
198
199     def private printPropertySetter(Type implementedIfc) '''
200         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
201         «val ifc = implementedIfc as GeneratedType»
202         «FOR getter : ifc.nonDefaultMethods»
203             «IF BindingMapping.isGetterMethodName(getter.name)»
204                 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
205             «ENDIF»
206         «ENDFOR»
207         «ENDIF»
208     '''
209
210     private def List<Type> getBaseIfcs(GeneratedType type) {
211         val List<Type> baseIfcs = new ArrayList();
212         for (ifc : type.implements) {
213             if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
214                 baseIfcs.add(ifc)
215             }
216         }
217         return baseIfcs
218     }
219
220     private def Set<Type> getAllIfcs(Type type) {
221         val Set<Type> baseIfcs = new HashSet()
222         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
223             val ifc = type as GeneratedType
224             for (impl : ifc.implements) {
225                 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
226                     baseIfcs.add(impl)
227                 }
228                 baseIfcs.addAll(impl.getAllIfcs)
229             }
230         }
231         return baseIfcs
232     }
233
234     private def List<String> toListOfNames(Collection<Type> types) {
235         val List<String> names = new ArrayList
236         for (type : types) {
237             names.add(type.fullyQualifiedName)
238         }
239         return names
240     }
241
242     def private constantsDeclarations() '''
243         «FOR c : type.getConstantDefinitions»
244             «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
245                 «val cValue = c.value as Map<String, String>»
246                 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
247                 «IF cValue.size == 1»
248                    private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«cValue.keySet.get(0).escapeJava»");
249                    private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«cValue.values.get(0).escapeJava»";
250                 «ELSE»
251                    private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
252                    «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
253                    private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
254                    FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
255                 «ENDIF»
256             «ELSE»
257                 «emitConstant(c)»
258             «ENDIF»
259         «ENDFOR»
260     '''
261
262     def private generateListSetter(GeneratedProperty field, Type actualType) '''
263         «val restrictions = restrictionsForSetter(actualType)»
264         «IF restrictions !== null»
265             «generateCheckers(field, restrictions, actualType)»
266         «ENDIF»
267         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
268         «IF restrictions !== null»
269             if (values != null) {
270                for («actualType.getFullyQualifiedName» value : values) {
271                    «checkArgument(field, restrictions, actualType, "value")»
272                }
273             }
274         «ENDIF»
275             this.«field.fieldName.toString» = values;
276             return this;
277         }
278
279     '''
280
281     def private generateSetter(GeneratedProperty field, Type actualType) '''
282         «val restrictions = restrictionsForSetter(actualType)»
283         «IF restrictions !== null»
284             «generateCheckers(field, restrictions, actualType)»
285         «ENDIF»
286
287         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
288         «IF restrictions !== null»
289             if (value != null) {
290                 «checkArgument(field, restrictions, actualType, "value")»
291             }
292         «ENDIF»
293             this.«field.fieldName.toString» = value;
294             return this;
295         }
296     '''
297
298     private def Type getActualType(ParameterizedType ptype) {
299         return ptype.getActualTypeArguments.get(0)
300     }
301
302     /**
303      * Template method which generates setter methods
304      *
305      * @return string with the setter methods
306      */
307     def private generateSetters() '''
308         «IF keyType !== null»
309             public «type.getName» withKey(final «keyType.importedName» key) {
310                 this.key = key;
311                 return this;
312             }
313         «ENDIF»
314         «FOR property : properties»
315             «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
316                 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
317             «ELSE»
318                 «generateSetter(property, property.returnType)»
319             «ENDIF»
320         «ENDFOR»
321
322         «IF augmentType !== null»
323             public «type.name» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
324                 if (augmentationValue == null) {
325                     return remove«AUGMENTATION_FIELD.toFirstUpper»(augmentationType);
326                 }
327
328                 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
329                     this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
330                 }
331
332                 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
333                 return this;
334             }
335
336             public «type.name» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType) {
337                 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
338                     this.«AUGMENTATION_FIELD».remove(augmentationType);
339                 }
340                 return this;
341             }
342         «ENDIF»
343     '''
344
345     private def createDescription(GeneratedType type) {
346         return '''
347         Class that builds {@link «type.importedName»} instances.
348
349         @see «type.importedName»
350     '''
351     }
352
353     override protected String formatDataForJavaDoc(GeneratedType type) {
354         val typeDescription = createDescription(type)
355
356         return '''
357             «IF !typeDescription.nullOrEmpty»
358             «typeDescription»
359             «ENDIF»
360         '''.toString
361     }
362
363     private def generateAugmentation() '''
364         @«SuppressWarnings.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
365         public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
366             return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
367         }
368     '''
369
370     override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
371         this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
372         «FOR field : keyProps»
373             this.«field.fieldName» = base.«field.getterMethodName»();
374         «ENDFOR»
375     '''
376
377     override protected generateCopyAugmentation(Type implType) {
378         val augmentationHolderRef = AugmentationHolder.importedName
379         val typeRef = targetType.importedName
380         val hashMapRef = HashMap.importedName
381         val augmentTypeRef = augmentType.importedName
382         return '''
383             if (base instanceof «augmentationHolderRef») {
384                 @SuppressWarnings("unchecked")
385                 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
386                 if (!aug.isEmpty()) {
387                     this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
388                 }
389             }
390         '''
391     }
392
393     private static def hasNonDefaultMethods(GeneratedType type) {
394         !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
395     }
396
397     private static def nonDefaultMethods(GeneratedType type) {
398         type.methodDefinitions.filter([def | !def.isDefault])
399     }
400 }