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