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