2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.java.api.generator
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
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
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
38 * Template for generating JAVA builder classes.
40 class BuilderTemplate extends AbstractBuilderTemplate {
42 * Constant used as suffix for builder name.
44 public static val BUILDER = "Builder";
47 * Constructs new instance of this class.
48 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
50 new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
52 super(genType, targetType, properties, augmentType, keyType)
55 override isLocalInnerClass(JavaTypeName name) {
56 // Builders do not have inner types
61 * Template method which generates JAVA class body for builder class and for IMPL class.
63 * @return string with JAVA source code
66 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
67 public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
69 «generateFields(false)»
71 «constantsDeclarations()»
73 «IF augmentType !== null»
74 «generateAugmentField()»
77 «generateConstructorsFromIfcs()»
79 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
81 «generateMethodFieldsFrom()»
83 «generateGetters(false)»
84 «IF augmentType !== null»
86 «generateAugmentation()»
91 @«Override.importedName»
92 public «targetType.name» build() {
93 return new «type.enclosedTypes.get(0).importedName»(this);
96 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
101 * Generate default constructor and constructor for every implemented interface from uses statements.
103 def private generateConstructorsFromIfcs() '''
104 public «type.name»() {
106 «IF (!(targetType instanceof GeneratedTransferObject))»
107 «FOR impl : targetType.implements»
108 «generateConstructorFromIfc(impl)»
114 * Generate constructor with argument of given type.
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)»
123 «FOR implTypeImplement : impl.implements»
124 «generateConstructorFromIfc(implTypeImplement)»
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»();
137 «FOR impl : ifc.implements»
138 «printConstructorPropertySetter(impl)»
144 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
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)»
156 «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
162 def private generateMethodFieldsFromComment(GeneratedType type) '''
164 * Set fields from given grouping argument. Valid argument is instance of one of following types:
166 «FOR impl : type.getAllIfcs»
167 * <li>«impl.fullyQualifiedName»</li>
171 * @param arg grouping object
172 * @throws IllegalArgumentException if given argument is none of valid types
177 * Method is used to find out if given type implements any interface from uses.
179 def boolean hasImplementsFromUses(GeneratedType type) {
181 for (impl : type.getAllIfcs) {
182 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
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)»
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»();
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) {
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) {
228 baseIfcs.addAll(impl.getAllIfcs)
234 private def List<String> toListOfNames(Collection<Type> types) {
235 val List<String> names = new ArrayList
237 names.add(type.fullyQualifiedName)
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»";
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» };
262 def private generateListSetter(GeneratedProperty field, Type actualType) '''
263 «val restrictions = restrictionsForSetter(actualType)»
264 «IF restrictions !== null»
265 «generateCheckers(field, restrictions, actualType)»
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")»
275 this.«field.fieldName.toString» = values;
281 def private generateSetter(GeneratedProperty field, Type actualType) '''
282 «val restrictions = restrictionsForSetter(actualType)»
283 «IF restrictions !== null»
284 «generateCheckers(field, restrictions, actualType)»
287 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
288 «IF restrictions !== null»
290 «checkArgument(field, restrictions, actualType, "value")»
293 this.«field.fieldName.toString» = value;
298 private def Type getActualType(ParameterizedType ptype) {
299 return ptype.getActualTypeArguments.get(0)
303 * Template method which generates setter methods
305 * @return string with the setter methods
307 def private generateSetters() '''
308 «IF keyType !== null»
309 public «type.getName» withKey(final «keyType.importedName» key) {
314 «FOR property : properties»
315 «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
316 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
318 «generateSetter(property, property.returnType)»
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);
328 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
329 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
332 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
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);
345 private def createDescription(GeneratedType targetType) {
346 val target = type.importedName
348 Class that builds {@link «target»} instances. Overall design of the class is that of a
349 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
352 In general, this class is supposed to be used like this template:
355 «target» createTarget(int fooXyzzy, int barBaz) {
356 return new «target»Builder()
357 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
358 .setBar(new BarBuilder().setBaz(barBaz).build())
365 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
366 worrying about synchronization issues.
369 As a side note: method chaining results in:
371 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
372 on the stack, so further method invocations just need to fill method arguments for the next method
373 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
374 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
376 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
377 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
378 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
383 @see «Builder.importedName»
387 override protected String formatDataForJavaDoc(GeneratedType type) {
388 val typeDescription = createDescription(type)
391 «IF !typeDescription.nullOrEmpty»
397 private def generateAugmentation() '''
398 @«SuppressWarnings.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
399 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
400 return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
404 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
405 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
406 «FOR field : keyProps»
407 this.«field.fieldName» = base.«field.getterMethodName»();
411 override protected generateCopyAugmentation(Type implType) {
412 val augmentationHolderRef = AugmentationHolder.importedName
413 val typeRef = targetType.importedName
414 val hashMapRef = HashMap.importedName
415 val augmentTypeRef = augmentType.importedName
417 if (base instanceof «augmentationHolderRef») {
418 @SuppressWarnings("unchecked")
419 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
420 if (!aug.isEmpty()) {
421 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
427 private static def hasNonDefaultMethods(GeneratedType type) {
428 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
431 private static def nonDefaultMethods(GeneratedType type) {
432 type.methodDefinitions.filter([def | !def.isDefault])