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