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, Set<BuilderGeneratedProperty> properties, Type augmentType,
53 super(genType, targetType, properties, augmentType, keyType)
56 override isLocalInnerClass(JavaTypeName name) {
57 // Builders do not have inner types
62 * Template method which generates JAVA class body for builder class and for IMPL class.
64 * @return string with JAVA source code
67 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
68 «targetType.annotations.generateDeprecatedAnnotation»
69 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
71 «generateFields(false)»
73 «constantsDeclarations()»
75 «IF augmentType !== null»
76 «generateAugmentField()»
79 «generateConstructorsFromIfcs()»
81 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
83 «generateMethodFieldsFrom()»
85 «generateGetters(false)»
86 «IF augmentType !== null»
88 «generateAugmentation()»
93 @«OVERRIDE.importedName»
94 public «targetType.name» build() {
95 return new «type.enclosedTypes.get(0).importedName»(this);
98 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
102 override generateDeprecatedAnnotation(AnnotationType ann) {
103 val forRemoval = ann.getParameter("forRemoval")
104 if (forRemoval !== null) {
105 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
107 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
111 * Generate default constructor and constructor for every implemented interface from uses statements.
113 def private generateConstructorsFromIfcs() '''
114 public «type.name»() {
116 «IF (!(targetType instanceof GeneratedTransferObject))»
117 «FOR impl : targetType.implements»
118 «generateConstructorFromIfc(impl)»
124 * Generate constructor with argument of given type.
126 def private Object generateConstructorFromIfc(Type impl) '''
127 «IF (impl instanceof GeneratedType)»
128 «IF impl.hasNonDefaultMethods»
129 public «type.name»(«impl.fullyQualifiedName» arg) {
130 «printConstructorPropertySetter(impl)»
133 «FOR implTypeImplement : impl.implements»
134 «generateConstructorFromIfc(implTypeImplement)»
139 def private Object printConstructorPropertySetter(Type implementedIfc) '''
140 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
141 «val ifc = implementedIfc as GeneratedType»
142 «FOR getter : ifc.nonDefaultMethods»
143 «IF BindingMapping.isGetterMethodName(getter.name)»
144 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
147 «FOR impl : ifc.implements»
148 «printConstructorPropertySetter(impl)»
154 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
156 def private generateMethodFieldsFrom() '''
157 «IF (!(targetType instanceof GeneratedTransferObject))»
158 «IF targetType.hasImplementsFromUses»
159 «val List<Type> done = targetType.getBaseIfcs»
160 «generateMethodFieldsFromComment(targetType)»
161 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
162 boolean isValidArg = false;
163 «FOR impl : targetType.getAllIfcs»
164 «generateIfCheck(impl, done)»
166 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
172 def private generateMethodFieldsFromComment(GeneratedType type) '''
174 * Set fields from given grouping argument. Valid argument is instance of one of following types:
176 «FOR impl : type.getAllIfcs»
177 * <li>«impl.fullyQualifiedName»</li>
181 * @param arg grouping object
182 * @throws IllegalArgumentException if given argument is none of valid types
187 * Method is used to find out if given type implements any interface from uses.
189 def boolean hasImplementsFromUses(GeneratedType type) {
191 for (impl : type.getAllIfcs) {
192 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
199 def private generateIfCheck(Type impl, List<Type> done) '''
200 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
201 «val implType = impl as GeneratedType»
202 if (arg instanceof «implType.fullyQualifiedName») {
203 «printPropertySetter(implType)»
209 def private printPropertySetter(Type implementedIfc) '''
210 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
211 «val ifc = implementedIfc as GeneratedType»
212 «FOR getter : ifc.nonDefaultMethods»
213 «IF BindingMapping.isGetterMethodName(getter.name)»
214 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
220 private def List<Type> getBaseIfcs(GeneratedType type) {
221 val List<Type> baseIfcs = new ArrayList();
222 for (ifc : type.implements) {
223 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
230 private def Set<Type> getAllIfcs(Type type) {
231 val Set<Type> baseIfcs = new HashSet()
232 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
233 val ifc = type as GeneratedType
234 for (impl : ifc.implements) {
235 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
238 baseIfcs.addAll(impl.getAllIfcs)
244 private def List<String> toListOfNames(Collection<Type> types) {
245 val List<String> names = new ArrayList
247 names.add(type.fullyQualifiedName)
252 def private constantsDeclarations() '''
253 «FOR c : type.getConstantDefinitions»
254 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
255 «val cValue = c.value as Map<String, String>»
256 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
257 «val jurPatternRef = JUR_PATTERN.importedName»
258 «IF cValue.size == 1»
259 «val firstEntry = cValue.entrySet.iterator.next»
260 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
261 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
263 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
264 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
265 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
266 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
274 def private generateSetter(GeneratedProperty field) {
275 val returnType = field.returnType
276 if (returnType instanceof ParameterizedType) {
277 if (Types.isListType(returnType)) {
278 return generateListSetter(field, returnType.actualTypeArguments.get(0))
279 } else if (Types.isMapType(returnType)) {
280 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
283 return generateSimpleSetter(field, returnType)
286 def private generateListSetter(GeneratedProperty field, Type actualType) '''
287 «val restrictions = restrictionsForSetter(actualType)»
288 «IF restrictions !== null»
289 «generateCheckers(field, restrictions, actualType)»
291 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
292 «IF restrictions !== null»
293 if (values != null) {
294 for («actualType.importedName» value : values) {
295 «checkArgument(field, restrictions, actualType, "value")»
299 this.«field.fieldName» = values;
305 // FIXME: MDSAL-540: remove the migration setter
306 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
307 «val restrictions = restrictionsForSetter(actualType)»
308 «val actualTypeRef = actualType.importedName»
309 «val setterName = "set" + field.name.toFirstUpper»
310 «IF restrictions !== null»
311 «generateCheckers(field, restrictions, actualType)»
313 public «type.getName» «setterName»(final «field.returnType.importedName» values) {
314 «IF restrictions !== null»
315 if (values != null) {
316 for («actualTypeRef» value : values.values()) {
317 «checkArgument(field, restrictions, actualType, "value")»
321 this.«field.fieldName» = values;
326 * Utility migration setter.
328 * <b>IMPORTANT NOTE</b>: This method does not completely match previous mechanics, as the list is processed as
329 * during this method's execution. Any future modifications of the list are <b>NOT</b>
330 * reflected in this builder nor its products.
332 * @param values Legacy List of values
333 * @return this builder
334 * @throws IllegalArgumentException if the list contains entries with the same key
335 * @throws NullPointerException if the list contains a null entry
336 * @deprecated Use {#link #«setterName»(«JU_MAP.importedName»)} instead.
338 @«DEPRECATED.importedName»(forRemoval = true)
339 public «type.getName» «setterName»(final «JU_LIST.importedName»<«actualTypeRef»> values) {
340 return «setterName»(«CODEHELPERS.importedName».compatMap(values));
344 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
345 «val restrictions = restrictionsForSetter(actualType)»
346 «IF restrictions !== null»
348 «generateCheckers(field, restrictions, actualType)»
351 «val setterName = "set" + field.getName.toFirstUpper»
352 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
353 «IF restrictions !== null»
355 «checkArgument(field, restrictions, actualType, "value")»
358 this.«field.fieldName» = value;
361 «val uintType = UINT_TYPES.get(field.returnType)»
362 «IF uintType !== null»
365 * Utility migration setter.
367 * @param value field value in legacy type
368 * @return this builder
369 * @deprecated Use {#link «setterName»(«field.returnType.importedJavadocName»)} instead.
371 @Deprecated(forRemoval = true)
372 public «type.getName» «setterName»(final «uintType.importedName» value) {
373 return «setterName»(«CODEHELPERS.importedName».compatUint(value));
379 * Template method which generates setter methods
381 * @return string with the setter methods
383 def private generateSetters() '''
384 «IF keyType !== null»
385 public «type.getName» withKey(final «keyType.importedName» key) {
390 «FOR property : properties»
391 «generateSetter(property)»
394 «IF augmentType !== null»
395 «val augmentTypeRef = augmentType.importedName»
396 «val jlClassRef = CLASS.importedName»
397 «val hashMapRef = JU_HASHMAP.importedName»
399 * Add an augmentation to this builder's product.
401 * @param augmentation augmentation to be added
402 * @return this builder
403 * @throws NullPointerException if {@code augmentation} is null
405 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
406 return doAddAugmentation(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
410 * Add or remove an augmentation to this builder's product.
412 * @param augmentationType augmentation type to be added or removed
413 * @param augmentationValue augmentation value, null if the augmentation type should be removed
414 * @return this builder
415 * @deprecated Use either {@link #addAugmentation(«augmentType.importedJavadocName»)} or {@link #removeAugmentation(«CLASS.importedName»)} instead.
417 @«DEPRECATED.importedName»(forRemoval = true)
418 public «type.name» addAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
419 return augmentationValue == null ? removeAugmentation(augmentationType) : doAddAugmentation(augmentationType, augmentationValue);
423 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
424 * type, this method does nothing.
426 * @param augmentationType augmentation type to be removed
427 * @return this builder
429 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
430 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
431 this.«AUGMENTATION_FIELD».remove(augmentationType);
436 private «type.name» doAddAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
437 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
438 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
441 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
447 private def createDescription(GeneratedType targetType) {
448 val target = type.importedName
450 Class that builds {@link «target»} instances. Overall design of the class is that of a
451 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
454 In general, this class is supposed to be used like this template:
457 «target» createTarget(int fooXyzzy, int barBaz) {
458 return new «target»Builder()
459 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
460 .setBar(new BarBuilder().setBaz(barBaz).build())
467 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
468 worrying about synchronization issues.
471 As a side note: method chaining results in:
473 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
474 on the stack, so further method invocations just need to fill method arguments for the next method
475 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
476 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
478 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
479 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
480 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
485 @see «BUILDER.importedName»
489 override protected String formatDataForJavaDoc(GeneratedType type) {
490 val typeDescription = createDescription(type)
493 «IF !typeDescription.nullOrEmpty»
499 private def generateAugmentation() '''
500 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
501 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
502 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
506 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
507 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
508 «FOR field : keyProps»
509 this.«field.fieldName» = base.«field.getterMethodName»();
513 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
515 this.«field.fieldName» = base.«field.getterName»();
519 override protected generateCopyAugmentation(Type implType) {
520 val augmentationHolderRef = AugmentationHolder.importedName
521 val typeRef = targetType.importedName
522 val hashMapRef = JU_HASHMAP.importedName
523 val augmentTypeRef = augmentType.importedName
525 if (base instanceof «augmentationHolderRef») {
526 @SuppressWarnings("unchecked")
527 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
528 if (!aug.isEmpty()) {
529 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
535 private static def hasNonDefaultMethods(GeneratedType type) {
536 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
539 private static def nonDefaultMethods(GeneratedType type) {
540 type.methodDefinitions.filter([def | !def.isDefault])