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.model.util.BindingTypes.DATA_OBJECT
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
13 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
16 import com.google.common.collect.ImmutableList
17 import java.util.ArrayList
18 import java.util.Collection
19 import java.util.HashSet
23 import org.opendaylight.mdsal.binding.model.api.AnnotationType
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
25 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType
27 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
28 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
29 import org.opendaylight.mdsal.binding.model.api.Type
30 import org.opendaylight.mdsal.binding.model.util.TypeConstants
31 import org.opendaylight.mdsal.binding.model.util.Types
32 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
33 import org.opendaylight.yangtools.concepts.Builder
34 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
37 * Template for generating JAVA builder classes.
39 class BuilderTemplate extends AbstractBuilderTemplate {
41 * Constant used as suffix for builder name.
43 package static val BUILDER_STR = "Builder";
45 static val BUILDER = JavaTypeName.create(Builder)
48 * Constructs new instance of this class.
49 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
51 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
52 super(genType, targetType, 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 «targetType.annotations.generateDeprecatedAnnotation»
68 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
70 «generateFields(false)»
72 «constantsDeclarations()»
74 «IF augmentType !== null»
75 «generateAugmentField()»
78 «generateConstructorsFromIfcs()»
80 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
82 «generateMethodFieldsFrom()»
84 «generateGetters(false)»
85 «IF augmentType !== null»
87 «generateAugmentation()»
92 @«OVERRIDE.importedName»
93 public «targetType.name» build() {
94 return new «type.enclosedTypes.get(0).importedName»(this);
97 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
101 override generateDeprecatedAnnotation(AnnotationType ann) {
102 val forRemoval = ann.getParameter("forRemoval")
103 if (forRemoval !== null) {
104 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
106 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
110 * Generate default constructor and constructor for every implemented interface from uses statements.
112 def private generateConstructorsFromIfcs() '''
113 public «type.name»() {
115 «IF (!(targetType instanceof GeneratedTransferObject))»
116 «FOR impl : targetType.implements»
117 «generateConstructorFromIfc(impl)»
123 * Generate constructor with argument of given type.
125 def private Object generateConstructorFromIfc(Type impl) '''
126 «IF (impl instanceof GeneratedType)»
127 «IF impl.hasNonDefaultMethods»
128 public «type.name»(«impl.fullyQualifiedName» arg) {
129 «printConstructorPropertySetter(impl)»
132 «FOR implTypeImplement : impl.implements»
133 «generateConstructorFromIfc(implTypeImplement)»
138 def private Object printConstructorPropertySetter(Type implementedIfc) '''
139 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
140 «val ifc = implementedIfc as GeneratedType»
141 «FOR getter : ifc.nonDefaultMethods»
142 «IF BindingMapping.isGetterMethodName(getter.name)»
143 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
146 «FOR impl : ifc.implements»
147 «printConstructorPropertySetter(impl)»
153 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
155 def private generateMethodFieldsFrom() '''
156 «IF (!(targetType instanceof GeneratedTransferObject))»
157 «IF targetType.hasImplementsFromUses»
158 «val List<Type> done = targetType.getBaseIfcs»
159 «generateMethodFieldsFromComment(targetType)»
160 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
161 boolean isValidArg = false;
162 «FOR impl : targetType.getAllIfcs»
163 «generateIfCheck(impl, done)»
165 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
171 def private generateMethodFieldsFromComment(GeneratedType type) '''
173 * Set fields from given grouping argument. Valid argument is instance of one of following types:
175 «FOR impl : type.getAllIfcs»
176 * <li>«impl.fullyQualifiedName»</li>
180 * @param arg grouping object
181 * @throws IllegalArgumentException if given argument is none of valid types
186 * Method is used to find out if given type implements any interface from uses.
188 def boolean hasImplementsFromUses(GeneratedType type) {
190 for (impl : type.getAllIfcs) {
191 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
198 def private generateIfCheck(Type impl, List<Type> done) '''
199 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
200 «val implType = impl as GeneratedType»
201 if (arg instanceof «implType.fullyQualifiedName») {
202 «printPropertySetter(implType)»
208 def private printPropertySetter(Type implementedIfc) '''
209 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
210 «val ifc = implementedIfc as GeneratedType»
211 «FOR getter : ifc.nonDefaultMethods»
212 «IF BindingMapping.isGetterMethodName(getter.name)»
213 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
219 private def List<Type> getBaseIfcs(GeneratedType type) {
220 val List<Type> baseIfcs = new ArrayList();
221 for (ifc : type.implements) {
222 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
229 private def Set<Type> getAllIfcs(Type type) {
230 val Set<Type> baseIfcs = new HashSet()
231 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
232 val ifc = type as GeneratedType
233 for (impl : ifc.implements) {
234 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
237 baseIfcs.addAll(impl.getAllIfcs)
243 private def List<String> toListOfNames(Collection<Type> types) {
244 val List<String> names = new ArrayList
246 names.add(type.fullyQualifiedName)
251 def private constantsDeclarations() '''
252 «FOR c : type.getConstantDefinitions»
253 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
254 «val cValue = c.value as Map<String, String>»
255 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
256 «val jurPatternRef = JUR_PATTERN.importedName»
257 «IF cValue.size == 1»
258 «val firstEntry = cValue.entrySet.iterator.next»
259 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
260 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
262 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
263 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
264 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
265 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
273 def private generateSetter(GeneratedProperty field) {
274 val returnType = field.returnType
275 if (returnType instanceof ParameterizedType) {
276 if (Types.isListType(returnType)) {
277 return generateListSetter(field, returnType.actualTypeArguments.get(0))
278 } else if (Types.isMapType(returnType)) {
279 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
282 return generateSimpleSetter(field, returnType)
285 def private generateListSetter(GeneratedProperty field, Type actualType) '''
286 «val restrictions = restrictionsForSetter(actualType)»
287 «IF restrictions !== null»
288 «generateCheckers(field, restrictions, actualType)»
290 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
291 «IF restrictions !== null»
292 if (values != null) {
293 for («actualType.importedName» value : values) {
294 «checkArgument(field, restrictions, actualType, "value")»
298 this.«field.fieldName» = values;
304 // FIXME: MDSAL-540: remove the migration setter
305 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
306 «val restrictions = restrictionsForSetter(actualType)»
307 «val actualTypeRef = actualType.importedName»
308 «val setterName = "set" + field.name.toFirstUpper»
309 «IF restrictions !== null»
310 «generateCheckers(field, restrictions, actualType)»
312 public «type.getName» «setterName»(final «field.returnType.importedName» values) {
313 «IF restrictions !== null»
314 if (values != null) {
315 for («actualTypeRef» value : values.values()) {
316 «checkArgument(field, restrictions, actualType, "value")»
320 this.«field.fieldName» = values;
325 * Utility migration setter.
327 * <b>IMPORTANT NOTE</b>: This method does not completely match previous mechanics, as the list is processed as
328 * during this method's execution. Any future modifications of the list are <b>NOT</b>
329 * reflected in this builder nor its products.
331 * @param values Legacy List of values
332 * @return this builder
333 * @throws IllegalArgumentException if the list contains entries with the same key
334 * @throws NullPointerException if the list contains a null entry
335 * @deprecated Use {#link #«setterName»(«JU_MAP.importedName»)} instead.
337 @«DEPRECATED.importedName»(forRemoval = true)
338 public «type.getName» «setterName»(final «JU_LIST.importedName»<«actualTypeRef»> values) {
339 return «setterName»(«CODEHELPERS.importedName».compatMap(values));
343 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
344 «val restrictions = restrictionsForSetter(actualType)»
345 «IF restrictions !== null»
347 «generateCheckers(field, restrictions, actualType)»
350 «val setterName = "set" + field.getName.toFirstUpper»
351 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
352 «IF restrictions !== null»
354 «checkArgument(field, restrictions, actualType, "value")»
357 this.«field.fieldName» = value;
360 «val uintType = UINT_TYPES.get(field.returnType)»
361 «IF uintType !== null»
364 * Utility migration setter.
366 * @param value field value in legacy type
367 * @return this builder
368 * @deprecated Use {#link «setterName»(«field.returnType.importedJavadocName»)} instead.
370 @Deprecated(forRemoval = true)
371 public «type.getName» «setterName»(final «uintType.importedName» value) {
372 return «setterName»(«CODEHELPERS.importedName».compatUint(value));
378 * Template method which generates setter methods
380 * @return string with the setter methods
382 def private generateSetters() '''
383 «IF keyType !== null»
384 public «type.getName» withKey(final «keyType.importedName» key) {
389 «FOR property : properties»
390 «generateSetter(property)»
393 «IF augmentType !== null»
394 «val augmentTypeRef = augmentType.importedName»
395 «val jlClassRef = CLASS.importedName»
396 «val hashMapRef = JU_HASHMAP.importedName»
398 * Add an augmentation to this builder's product.
400 * @param augmentation augmentation to be added
401 * @return this builder
402 * @throws NullPointerException if {@code augmentation} is null
404 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
405 return doAddAugmentation(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
409 * Add or remove an augmentation to this builder's product.
411 * @param augmentationType augmentation type to be added or removed
412 * @param augmentationValue augmentation value, null if the augmentation type should be removed
413 * @return this builder
414 * @deprecated Use either {@link #addAugmentation(«augmentType.importedJavadocName»)} or {@link #removeAugmentation(«CLASS.importedName»)} instead.
416 @«DEPRECATED.importedName»(forRemoval = true)
417 public «type.name» addAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
418 return augmentationValue == null ? removeAugmentation(augmentationType) : doAddAugmentation(augmentationType, augmentationValue);
422 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
423 * type, this method does nothing.
425 * @param augmentationType augmentation type to be removed
426 * @return this builder
428 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
429 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
430 this.«AUGMENTATION_FIELD».remove(augmentationType);
435 private «type.name» doAddAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
436 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
437 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
440 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
446 private def createDescription(GeneratedType targetType) {
447 val target = type.importedName
449 Class that builds {@link «target»} instances. Overall design of the class is that of a
450 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
453 In general, this class is supposed to be used like this template:
456 «target» createTarget(int fooXyzzy, int barBaz) {
457 return new «target»Builder()
458 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
459 .setBar(new BarBuilder().setBaz(barBaz).build())
466 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
467 worrying about synchronization issues.
470 As a side note: method chaining results in:
472 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
473 on the stack, so further method invocations just need to fill method arguments for the next method
474 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
475 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
477 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
478 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
479 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
484 @see «BUILDER.importedName»
488 override protected String formatDataForJavaDoc(GeneratedType type) {
489 val typeDescription = createDescription(type)
492 «IF !typeDescription.nullOrEmpty»
498 private def generateAugmentation() '''
499 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
500 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
501 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
505 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
506 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
507 «FOR field : keyProps»
508 this.«field.fieldName» = base.«field.getterMethodName»();
512 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
514 this.«field.fieldName» = base.«field.getterName»();
518 override protected generateCopyAugmentation(Type implType) {
519 val augmentationHolderRef = AugmentationHolder.importedName
520 val typeRef = targetType.importedName
521 val hashMapRef = JU_HASHMAP.importedName
522 val augmentTypeRef = augmentType.importedName
524 if (base instanceof «augmentationHolderRef») {
525 @SuppressWarnings("unchecked")
526 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
527 if (!aug.isEmpty()) {
528 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
534 private static def hasNonDefaultMethods(GeneratedType type) {
535 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
538 private static def nonDefaultMethods(GeneratedType type) {
539 type.methodDefinitions.filter([def | !def.isDefault])