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.ri.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.ri.TypeConstants
34 import org.opendaylight.mdsal.binding.model.ri.Types
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
38 * Template for generating JAVA builder classes.
40 class BuilderTemplate extends AbstractBuilderTemplate {
41 val BuilderImplTemplate implTemplate
44 * Constructs new instance of this class.
45 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
47 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
48 super(genType, targetType, keyType)
49 implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
52 override isLocalInnerClass(JavaTypeName name) {
53 // Builders do not have inner types
58 * Template method which generates JAVA class body for builder class and for IMPL class.
60 * @return string with JAVA source code
63 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
64 «targetType.annotations.generateDeprecatedAnnotation»
66 public class «type.name» {
68 «generateFields(false)»
70 «constantsDeclarations()»
72 «IF augmentType !== null»
73 «val augmentTypeRef = augmentType.importedName»
74 «val mapTypeRef = JU_MAP.importedName»
75 «mapTypeRef»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> «AUGMENTATION_FIELD» = «mapTypeRef».of();
79 * Construct an empty builder.
81 public «type.name»() {
85 «generateConstructorsFromIfcs()»
87 «val targetTypeName = targetType.importedName»
89 * Construct a builder initialized with state from specified {@link «targetTypeName»}.
91 * @param base «targetTypeName» from which the builder should be initialized
93 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
95 «generateMethodFieldsFrom()»
97 «generateGetters(false)»
98 «IF augmentType !== null»
100 «generateAugmentation()»
106 * A new {@link «targetTypeName»} instance.
108 * @return A new {@link «targetTypeName»} instance.
110 public «targetType.importedNonNull» build() {
111 return new «type.enclosedTypes.get(0).importedName»(this);
118 override generateDeprecatedAnnotation(AnnotationType ann) {
119 val forRemoval = ann.getParameter("forRemoval")
120 if (forRemoval !== null) {
121 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
123 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
127 * Generate default constructor and constructor for every implemented interface from uses statements.
129 def private generateConstructorsFromIfcs() '''
130 «IF (!(targetType instanceof GeneratedTransferObject))»
131 «FOR impl : targetType.implements SEPARATOR "\n"»
132 «generateConstructorFromIfc(impl)»
138 * Generate constructor with argument of given type.
140 def private Object generateConstructorFromIfc(Type impl) '''
141 «IF (impl instanceof GeneratedType)»
142 «IF impl.hasNonDefaultMethods»
143 «val typeName = impl.importedName»
145 * Construct a new builder initialized from specified {@link «typeName»}.
147 * @param arg «typeName» from which the builder should be initialized
149 public «type.name»(«typeName» arg) {
150 «printConstructorPropertySetter(impl)»
154 «FOR implTypeImplement : impl.implements»
155 «generateConstructorFromIfc(implTypeImplement)»
160 def private Object printConstructorPropertySetter(Type implementedIfc) '''
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)»
165 «val propertyName = getter.propertyNameFromGetter»
166 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
169 «FOR impl : ifc.implements»
170 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
175 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
176 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
177 «val ifc = implementedIfc as GeneratedType»
178 «FOR getter : ifc.nonDefaultMethods»
179 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
180 «val propertyName = getter.propertyNameFromGetter»
181 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
184 «FOR descendant : ifc.implements»
185 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
190 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
191 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
192 for (MethodSignature method : type.getMethodDefinitions()) {
193 if (method.hasOverrideAnnotation) {
194 setBuilder.add(method)
197 return setBuilder.build()
201 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
203 def private generateMethodFieldsFrom() '''
204 «IF (!(targetType instanceof GeneratedTransferObject))»
205 «IF targetType.hasImplementsFromUses»
206 «val List<Type> done = targetType.getBaseIfcs»
207 «generateMethodFieldsFromComment(targetType)»
208 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
209 boolean isValidArg = false;
210 «FOR impl : targetType.getAllIfcs»
211 «generateIfCheck(impl, done)»
213 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
219 def private generateMethodFieldsFromComment(GeneratedType type) '''
221 * Set fields from given grouping argument. Valid argument is instance of one of following types:
223 «FOR impl : type.getAllIfcs»
224 * <li>{@link «impl.importedName»}</li>
228 * @param arg grouping object
229 * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
234 * Method is used to find out if given type implements any interface from uses.
236 def boolean hasImplementsFromUses(GeneratedType type) {
238 for (impl : type.getAllIfcs) {
239 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
246 def private generateIfCheck(Type impl, List<Type> done) '''
247 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
248 «val implType = impl as GeneratedType»
249 if (arg instanceof «implType.importedName») {
250 «printPropertySetter(implType)»
256 def private printPropertySetter(Type implementedIfc) '''
257 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
258 «val ifc = implementedIfc as GeneratedType»
259 «FOR getter : ifc.nonDefaultMethods»
260 «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
261 «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
267 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
268 val ownGetter = implTemplate.findGetter(getter.name)
269 val ownGetterType = ownGetter.returnType
270 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
271 return "this._" + propertyName + " = " + retrieveProperty
273 if (ownGetterType instanceof ParameterizedType) {
274 val itemType = ownGetterType.actualTypeArguments.get(0)
275 if (Types.isListType(ownGetterType)) {
276 val importedClass = importedClass(itemType)
277 if (importedClass !== null) {
278 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass)
280 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
282 if (Types.isSetType(ownGetterType)) {
283 val importedClass = importedClass(itemType)
284 if (importedClass !== null) {
285 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass)
287 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
289 if (Types.CLASS.equals(ownGetterType)) {
290 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName)
293 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
296 def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
297 this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
299 private def importedClass(Type type) {
300 if (type instanceof ParameterizedType) {
301 if (Types.CLASS.equals(type.rawType)) {
302 return type.actualTypeArguments.get(0).identifier.importedName
308 private def List<Type> getBaseIfcs(GeneratedType type) {
309 val List<Type> baseIfcs = new ArrayList();
310 for (ifc : type.implements) {
311 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
318 private def Set<Type> getAllIfcs(Type type) {
319 val Set<Type> baseIfcs = new HashSet()
320 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
321 val ifc = type as GeneratedType
322 for (impl : ifc.implements) {
323 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
326 baseIfcs.addAll(impl.getAllIfcs)
332 private def List<String> toListOfNames(Collection<Type> types) {
333 val List<String> names = new ArrayList
335 names.add(type.importedName)
340 def private constantsDeclarations() '''
341 «FOR c : type.getConstantDefinitions»
342 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
343 «val cValue = c.value as Map<String, String>»
344 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
345 «val jurPatternRef = JUR_PATTERN.importedName»
346 «IF cValue.size == 1»
347 «val firstEntry = cValue.entrySet.iterator.next»
348 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
349 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
351 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
352 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
353 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
354 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
362 def private generateSetter(BuilderGeneratedProperty field) {
363 val returnType = field.returnType
364 if (returnType instanceof ParameterizedType) {
365 if (Types.isListType(returnType) || Types.isSetType(returnType)) {
366 val arguments = returnType.actualTypeArguments
367 if (arguments.isEmpty) {
368 return generateListSetter(field, Types.objectType)
370 return generateListSetter(field, arguments.get(0))
371 } else if (Types.isMapType(returnType)) {
372 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
375 return generateSimpleSetter(field, returnType)
378 def private generateListSetter(BuilderGeneratedProperty field, Type actualType) '''
379 «val restrictions = restrictionsForSetter(actualType)»
380 «IF restrictions !== null»
381 «generateCheckers(field, restrictions, actualType)»
385 * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
388 * @param values desired value
389 * @return this builder
391 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
392 «IF restrictions !== null»
393 if (values != null) {
394 for («actualType.importedName» value : values) {
395 «checkArgument(field, restrictions, actualType, "value")»
399 this.«field.fieldName» = values;
405 def private generateMapSetter(BuilderGeneratedProperty field, Type actualType) '''
406 «val restrictions = restrictionsForSetter(actualType)»
407 «IF restrictions !== null»
408 «generateCheckers(field, restrictions, actualType)»
412 * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
415 * @param values desired value
416 * @return this builder
418 public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
419 «IF restrictions !== null»
420 if (values != null) {
421 for («actualType.importedName» value : values.values()) {
422 «checkArgument(field, restrictions, actualType, "value")»
426 this.«field.fieldName» = values;
431 def private generateSimpleSetter(BuilderGeneratedProperty field, Type actualType) '''
432 «val restrictions = restrictionsForSetter(actualType)»
433 «IF restrictions !== null»
435 «generateCheckers(field, restrictions, actualType)»
439 * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
442 * @param value desired value
443 * @return this builder
445 «val setterName = "set" + field.getName.toFirstUpper»
446 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
447 «IF restrictions !== null»
449 «checkArgument(field, restrictions, actualType, "value")»
452 this.«field.fieldName» = value;
458 * Template method which generates setter methods
460 * @return string with the setter methods
462 def private generateSetters() '''
463 «IF keyType !== null»
464 public «type.getName» withKey(final «keyType.importedName» key) {
469 «FOR property : properties»
470 «generateSetter(property)»
473 «IF augmentType !== null»
474 «val augmentTypeRef = augmentType.importedName»
475 «val hashMapRef = JU_HASHMAP.importedName»
477 * Add an augmentation to this builder's product.
479 * @param augmentation augmentation to be added
480 * @return this builder
481 * @throws NullPointerException if {@code augmentation} is null
483 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
484 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
485 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
488 this.«AUGMENTATION_FIELD».put(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
493 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
494 * type, this method does nothing.
496 * @param augmentationType augmentation type to be removed
497 * @return this builder
499 public «type.name» removeAugmentation(«CLASS.importedName»<? extends «augmentTypeRef»> augmentationType) {
500 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
501 this.«AUGMENTATION_FIELD».remove(augmentationType);
508 private def createDescription(GeneratedType targetType) {
509 val target = targetType.importedName
511 Class that builds {@link «target»} instances. Overall design of the class is that of a
512 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
515 In general, this class is supposed to be used like this template:
518 «target» create«target»(int fooXyzzy, int barBaz) {
519 return new «target»Builder()
520 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
521 .setBar(new BarBuilder().setBaz(barBaz).build())
528 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
529 worrying about synchronization issues.
532 As a side note: method chaining results in:
534 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
535 on the stack, so further method invocations just need to fill method arguments for the next method
536 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
537 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
539 <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
540 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
541 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
549 override protected String formatDataForJavaDoc(GeneratedType type) {
550 val typeDescription = createDescription(type)
553 «IF !typeDescription.nullOrEmpty»
559 private def generateAugmentation() '''
561 * Return the specified augmentation, if it is present in this builder.
563 * @param <E$$> augmentation type
564 * @param augmentationType augmentation type class
565 * @return Augmentation object from this builder, or {@code null} if not present
566 * @throws «NPE.importedName» if {@code augmentType} is {@code null}
568 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
569 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
570 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
574 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
575 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
576 «FOR field : keyProps»
577 this.«field.fieldName» = base.«field.getterMethodName»();
581 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
583 this.«field.fieldName» = base.«field.getterName»();
587 override protected generateCopyAugmentation(Type implType) '''
588 final var aug = base.augmentations();
589 if (!aug.isEmpty()) {
590 this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug);