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 com.google.common.collect.ImmutableSet
18 import com.google.common.collect.Sets
19 import java.util.ArrayList
20 import java.util.Collection
21 import java.util.HashSet
25 import org.opendaylight.mdsal.binding.model.api.AnnotationType
26 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
27 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType
29 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
30 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
31 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
32 import org.opendaylight.mdsal.binding.model.api.Type
33 import org.opendaylight.mdsal.binding.model.util.TypeConstants
34 import org.opendaylight.mdsal.binding.model.util.Types
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
36 import org.opendaylight.yangtools.concepts.Builder
39 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends AbstractBuilderTemplate {
43 * Constant used as suffix for builder name.
45 package static val BUILDER_STR = "Builder";
47 static val BUILDER = JavaTypeName.create(Builder)
49 val BuilderImplTemplate implTemplate
52 * Constructs new instance of this class.
53 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
55 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
56 super(genType, targetType, keyType)
57 implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
60 override isLocalInnerClass(JavaTypeName name) {
61 // Builders do not have inner types
66 * Template method which generates JAVA class body for builder class and for IMPL class.
68 * @return string with JAVA source code
71 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
72 «targetType.annotations.generateDeprecatedAnnotation»
74 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
76 «generateFields(false)»
78 «constantsDeclarations()»
80 «IF augmentType !== null»
81 «generateAugmentField()»
84 «generateConstructorsFromIfcs()»
86 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
88 «generateMethodFieldsFrom()»
90 «generateGetters(false)»
91 «IF augmentType !== null»
93 «generateAugmentation()»
98 @«OVERRIDE.importedName»
99 public «targetType.name» build() {
100 return new «type.enclosedTypes.get(0).importedName»(this);
107 override generateDeprecatedAnnotation(AnnotationType ann) {
108 val forRemoval = ann.getParameter("forRemoval")
109 if (forRemoval !== null) {
110 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
112 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
116 * Generate default constructor and constructor for every implemented interface from uses statements.
118 def private generateConstructorsFromIfcs() '''
119 public «type.name»() {
122 «IF (!(targetType instanceof GeneratedTransferObject))»
123 «FOR impl : targetType.implements SEPARATOR "\n"»
124 «generateConstructorFromIfc(impl)»
130 * Generate constructor with argument of given type.
132 def private Object generateConstructorFromIfc(Type impl) '''
133 «IF (impl instanceof GeneratedType)»
134 «IF impl.hasNonDefaultMethods»
135 public «type.name»(«impl.fullyQualifiedName» arg) {
136 «printConstructorPropertySetter(impl)»
139 «FOR implTypeImplement : impl.implements»
140 «generateConstructorFromIfc(implTypeImplement)»
145 def private Object printConstructorPropertySetter(Type implementedIfc) '''
146 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
147 «val ifc = implementedIfc as GeneratedType»
148 «FOR getter : ifc.nonDefaultMethods»
149 «IF BindingMapping.isGetterMethodName(getter.name)»
150 «val propertyName = getter.propertyNameFromGetter»
151 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
154 «FOR impl : ifc.implements»
155 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
160 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
161 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
162 «val ifc = implementedIfc as GeneratedType»
163 «FOR getter : ifc.nonDefaultMethods»
164 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
165 «val propertyName = getter.propertyNameFromGetter»
166 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
169 «FOR descendant : ifc.implements»
170 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
175 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
176 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
177 for (MethodSignature method : type.getMethodDefinitions()) {
178 if (method.hasOverrideAnnotation) {
179 setBuilder.add(method)
182 return setBuilder.build()
186 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
188 def private generateMethodFieldsFrom() '''
189 «IF (!(targetType instanceof GeneratedTransferObject))»
190 «IF targetType.hasImplementsFromUses»
191 «val List<Type> done = targetType.getBaseIfcs»
192 «generateMethodFieldsFromComment(targetType)»
193 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
194 boolean isValidArg = false;
195 «FOR impl : targetType.getAllIfcs»
196 «generateIfCheck(impl, done)»
198 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
204 def private generateMethodFieldsFromComment(GeneratedType type) '''
206 * Set fields from given grouping argument. Valid argument is instance of one of following types:
208 «FOR impl : type.getAllIfcs»
209 * <li>«impl.fullyQualifiedName»</li>
213 * @param arg grouping object
214 * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
219 * Method is used to find out if given type implements any interface from uses.
221 def boolean hasImplementsFromUses(GeneratedType type) {
223 for (impl : type.getAllIfcs) {
224 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
231 def private generateIfCheck(Type impl, List<Type> done) '''
232 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
233 «val implType = impl as GeneratedType»
234 if (arg instanceof «implType.fullyQualifiedName») {
235 «printPropertySetter(implType)»
241 def private printPropertySetter(Type implementedIfc) '''
242 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
243 «val ifc = implementedIfc as GeneratedType»
244 «FOR getter : ifc.nonDefaultMethods»
245 «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
246 «printPropertySetter(getter, '''((«ifc.fullyQualifiedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
252 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
253 val ownGetter = implTemplate.findGetter(getter.name)
254 val ownGetterType = ownGetter.returnType
255 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
256 return "this._" + propertyName + " = " + retrieveProperty
258 if (Types.isListType(ownGetterType)) {
259 val itemType = (ownGetterType as ParameterizedType).actualTypeArguments.get(0)
261 this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)'''
264 this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetter.returnType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)'''
267 private def List<Type> getBaseIfcs(GeneratedType type) {
268 val List<Type> baseIfcs = new ArrayList();
269 for (ifc : type.implements) {
270 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
277 private def Set<Type> getAllIfcs(Type type) {
278 val Set<Type> baseIfcs = new HashSet()
279 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
280 val ifc = type as GeneratedType
281 for (impl : ifc.implements) {
282 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
285 baseIfcs.addAll(impl.getAllIfcs)
291 private def List<String> toListOfNames(Collection<Type> types) {
292 val List<String> names = new ArrayList
294 names.add(type.fullyQualifiedName)
299 def private constantsDeclarations() '''
300 «FOR c : type.getConstantDefinitions»
301 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
302 «val cValue = c.value as Map<String, String>»
303 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
304 «val jurPatternRef = JUR_PATTERN.importedName»
305 «IF cValue.size == 1»
306 «val firstEntry = cValue.entrySet.iterator.next»
307 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
308 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
310 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
311 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
312 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
313 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
321 def private generateSetter(GeneratedProperty field) {
322 val returnType = field.returnType
323 if (returnType instanceof ParameterizedType) {
324 if (Types.isListType(returnType)) {
325 val arguments = returnType.actualTypeArguments
326 if (arguments.isEmpty) {
327 return generateListSetter(field, Types.objectType)
329 return generateListSetter(field, arguments.get(0))
330 } else if (Types.isMapType(returnType)) {
331 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
334 return generateSimpleSetter(field, returnType)
337 def private generateListSetter(GeneratedProperty field, Type actualType) '''
338 «val restrictions = restrictionsForSetter(actualType)»
339 «IF restrictions !== null»
340 «generateCheckers(field, restrictions, actualType)»
342 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
343 «IF restrictions !== null»
344 if (values != null) {
345 for («actualType.importedName» value : values) {
346 «checkArgument(field, restrictions, actualType, "value")»
350 this.«field.fieldName» = values;
356 // FIXME: MDSAL-540: remove the migration setter
357 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
358 «val restrictions = restrictionsForSetter(actualType)»
359 «val actualTypeRef = actualType.importedName»
360 «val setterName = "set" + field.name.toFirstUpper»
361 «IF restrictions !== null»
362 «generateCheckers(field, restrictions, actualType)»
364 public «type.getName» «setterName»(final «field.returnType.importedName» values) {
365 «IF restrictions !== null»
366 if (values != null) {
367 for («actualTypeRef» value : values.values()) {
368 «checkArgument(field, restrictions, actualType, "value")»
372 this.«field.fieldName» = values;
377 * Utility migration setter.
379 * <b>IMPORTANT NOTE</b>: This method does not completely match previous mechanics, as the list is processed as
380 * during this method's execution. Any future modifications of the list are <b>NOT</b>
381 * reflected in this builder nor its products.
383 * @param values Legacy List of values
384 * @return this builder
385 * @throws IllegalArgumentException if the list contains entries with the same key
386 * @throws NullPointerException if the list contains a null entry
387 * @deprecated Use {@link #«setterName»(«JU_MAP.importedName»)} instead.
389 @«DEPRECATED.importedName»(forRemoval = true)
390 public «type.getName» «setterName»(final «JU_LIST.importedName»<«actualTypeRef»> values) {
391 return «setterName»(«CODEHELPERS.importedName».compatMap(values));
395 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
396 «val restrictions = restrictionsForSetter(actualType)»
397 «IF restrictions !== null»
399 «generateCheckers(field, restrictions, actualType)»
402 «val setterName = "set" + field.getName.toFirstUpper»
403 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
404 «IF restrictions !== null»
406 «checkArgument(field, restrictions, actualType, "value")»
409 this.«field.fieldName» = value;
412 «val uintType = UINT_TYPES.get(field.returnType)»
413 «IF uintType !== null»
416 * Utility migration setter.
418 * @param value field value in legacy type
419 * @return this builder
420 * @deprecated Use {@link #«setterName»(«field.returnType.importedJavadocName»)} instead.
422 @Deprecated(forRemoval = true)
423 public «type.getName» «setterName»(final «uintType.importedName» value) {
424 return «setterName»(«CODEHELPERS.importedName».compatUint(value));
430 * Template method which generates setter methods
432 * @return string with the setter methods
434 def private generateSetters() '''
435 «IF keyType !== null»
436 public «type.getName» withKey(final «keyType.importedName» key) {
441 «FOR property : properties»
442 «generateSetter(property)»
445 «IF augmentType !== null»
446 «val augmentTypeRef = augmentType.importedName»
447 «val jlClassRef = CLASS.importedName»
448 «val hashMapRef = JU_HASHMAP.importedName»
450 * Add an augmentation to this builder's product.
452 * @param augmentation augmentation to be added
453 * @return this builder
454 * @throws NullPointerException if {@code augmentation} is null
456 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
457 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
458 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
459 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
462 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
467 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
468 * type, this method does nothing.
470 * @param augmentationType augmentation type to be removed
471 * @return this builder
473 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
474 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
475 this.«AUGMENTATION_FIELD».remove(augmentationType);
482 private def createDescription(GeneratedType targetType) {
483 val target = type.importedName
485 Class that builds {@link «target»} instances. Overall design of the class is that of a
486 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
489 In general, this class is supposed to be used like this template:
492 «target» createTarget(int fooXyzzy, int barBaz) {
493 return new «target»Builder()
494 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
495 .setBar(new BarBuilder().setBaz(barBaz).build())
502 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
503 worrying about synchronization issues.
506 As a side note: method chaining results in:
508 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
509 on the stack, so further method invocations just need to fill method arguments for the next method
510 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
511 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
513 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
514 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
515 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
520 @see «BUILDER.importedName»
524 override protected String formatDataForJavaDoc(GeneratedType type) {
525 val typeDescription = createDescription(type)
528 «IF !typeDescription.nullOrEmpty»
534 private def generateAugmentation() '''
535 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
536 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
537 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
541 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
542 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
543 «FOR field : keyProps»
544 this.«field.fieldName» = base.«field.getterMethodName»();
548 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
550 this.«field.fieldName» = base.«field.getterName»();
554 override protected generateCopyAugmentation(Type implType) {
555 val hashMapRef = JU_HASHMAP.importedName
556 val augmentTypeRef = augmentType.importedName
558 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
559 if (!aug.isEmpty()) {
560 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);