package org.opendaylight.mdsal.binding.java.api.generator
import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
+import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.DATA_OBJECT
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME
import com.google.common.collect.ImmutableList
+import com.google.common.collect.ImmutableSet
+import com.google.common.collect.Sets
import java.util.ArrayList
import java.util.Collection
-import java.util.HashMap
import java.util.HashSet
import java.util.List
import java.util.Map
import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
import org.opendaylight.mdsal.binding.model.api.GeneratedType
import org.opendaylight.mdsal.binding.model.api.JavaTypeName
+import org.opendaylight.mdsal.binding.model.api.MethodSignature;
import org.opendaylight.mdsal.binding.model.api.ParameterizedType
import org.opendaylight.mdsal.binding.model.api.Type
-import org.opendaylight.mdsal.binding.model.util.TypeConstants
-import org.opendaylight.mdsal.binding.model.util.Types
+import org.opendaylight.mdsal.binding.model.ri.TypeConstants
+import org.opendaylight.mdsal.binding.model.ri.Types
import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
-import org.opendaylight.yangtools.concepts.Builder
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder
-import org.opendaylight.yangtools.yang.binding.DataObject
/**
* Template for generating JAVA builder classes.
*/
class BuilderTemplate extends AbstractBuilderTemplate {
- /**
- * Constant used as suffix for builder name.
- */
- public static val BUILDER = "Builder";
-
- static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
+ val BuilderImplTemplate implTemplate
/**
* Constructs new instance of this class.
* @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
*/
- new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
- Type keyType) {
- super(genType, targetType, properties, augmentType, keyType)
+ new(GeneratedType genType, GeneratedType targetType, Type keyType) {
+ super(genType, targetType, keyType)
+ implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
}
override isLocalInnerClass(JavaTypeName name) {
override body() '''
«wrapToDocumentation(formatDataForJavaDoc(targetType))»
«targetType.annotations.generateDeprecatedAnnotation»
- public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
+ «generatedAnnotation»
+ public class «type.name» {
«generateFields(false)»
«constantsDeclarations()»
«IF augmentType !== null»
- «generateAugmentField()»
+ «val augmentTypeRef = augmentType.importedName»
+ «val mapTypeRef = JU_MAP.importedName»
+ «mapTypeRef»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> «AUGMENTATION_FIELD» = «mapTypeRef».of();
«ENDIF»
+ /**
+ * Construct an empty builder.
+ */
+ public «type.name»() {
+ // No-op
+ }
+
«generateConstructorsFromIfcs()»
+ «val targetTypeName = targetType.importedName»
+ /**
+ * Construct a builder initialized with state from specified {@link «targetTypeName»}.
+ *
+ * @param base «targetTypeName» from which the builder should be initialized
+ */
public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
«generateMethodFieldsFrom()»
«generateSetters»
- @«OVERRIDE.importedName»
- public «targetType.name» build() {
+ /**
+ * A new {@link «targetTypeName»} instance.
+ *
+ * @return A new {@link «targetTypeName»} instance.
+ */
+ public «targetType.importedNonNull» build() {
return new «type.enclosedTypes.get(0).importedName»(this);
}
- «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
+ «implTemplate.body»
}
'''
* Generate default constructor and constructor for every implemented interface from uses statements.
*/
def private generateConstructorsFromIfcs() '''
- public «type.name»() {
- }
«IF (!(targetType instanceof GeneratedTransferObject))»
- «FOR impl : targetType.implements»
+ «FOR impl : targetType.implements SEPARATOR "\n"»
«generateConstructorFromIfc(impl)»
«ENDFOR»
«ENDIF»
def private Object generateConstructorFromIfc(Type impl) '''
«IF (impl instanceof GeneratedType)»
«IF impl.hasNonDefaultMethods»
- public «type.name»(«impl.fullyQualifiedName» arg) {
+ «val typeName = impl.importedName»
+ /**
+ * Construct a new builder initialized from specified {@link «typeName»}.
+ *
+ * @param arg «typeName» from which the builder should be initialized
+ */
+ public «type.name»(«typeName» arg) {
«printConstructorPropertySetter(impl)»
}
+
«ENDIF»
«FOR implTypeImplement : impl.implements»
«generateConstructorFromIfc(implTypeImplement)»
«val ifc = implementedIfc as GeneratedType»
«FOR getter : ifc.nonDefaultMethods»
«IF BindingMapping.isGetterMethodName(getter.name)»
- this._«getter.propertyNameFromGetter» = arg.«getter.name»();
+ «val propertyName = getter.propertyNameFromGetter»
+ «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
«ENDIF»
«ENDFOR»
«FOR impl : ifc.implements»
- «printConstructorPropertySetter(impl)»
+ «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
+ «ENDFOR»
+ «ENDIF»
+ '''
+
+ def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
+ «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
+ «val ifc = implementedIfc as GeneratedType»
+ «FOR getter : ifc.nonDefaultMethods»
+ «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
+ «val propertyName = getter.propertyNameFromGetter»
+ «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
+ «ENDIF»
+ «ENDFOR»
+ «FOR descendant : ifc.implements»
+ «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
«ENDFOR»
«ENDIF»
'''
+ def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
+ val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
+ for (MethodSignature method : type.getMethodDefinitions()) {
+ if (method.hasOverrideAnnotation) {
+ setBuilder.add(method)
+ }
+ }
+ return setBuilder.build()
+ }
+
/**
* Generate 'fieldsFrom' method to set builder properties based on type of given argument.
*/
«IF targetType.hasImplementsFromUses»
«val List<Type> done = targetType.getBaseIfcs»
«generateMethodFieldsFromComment(targetType)»
- public void fieldsFrom(«DataObject.importedName» arg) {
+ public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
boolean isValidArg = false;
«FOR impl : targetType.getAllIfcs»
«generateIfCheck(impl, done)»
* Set fields from given grouping argument. Valid argument is instance of one of following types:
* <ul>
«FOR impl : type.getAllIfcs»
- * <li>«impl.fullyQualifiedName»</li>
+ * <li>{@link «impl.importedName»}</li>
«ENDFOR»
* </ul>
*
* @param arg grouping object
- * @throws IllegalArgumentException if given argument is none of valid types
+ * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
*/
'''
def private generateIfCheck(Type impl, List<Type> done) '''
«IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
«val implType = impl as GeneratedType»
- if (arg instanceof «implType.fullyQualifiedName») {
+ if (arg instanceof «implType.importedName») {
«printPropertySetter(implType)»
isValidArg = true;
}
«IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
«val ifc = implementedIfc as GeneratedType»
«FOR getter : ifc.nonDefaultMethods»
- «IF BindingMapping.isGetterMethodName(getter.name)»
- this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
+ «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
+ «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
«ENDIF»
«ENDFOR»
«ENDIF»
'''
+ def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
+ val ownGetter = implTemplate.findGetter(getter.name)
+ val ownGetterType = ownGetter.returnType
+ if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
+ return "this._" + propertyName + " = " + retrieveProperty
+ }
+ if (ownGetterType instanceof ParameterizedType) {
+ val itemType = ownGetterType.actualTypeArguments.get(0)
+ if (Types.isListType(ownGetterType)) {
+ return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
+ }
+ if (Types.isSetType(ownGetterType)) {
+ return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
+ }
+ }
+ return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
+ }
+
+ def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
+ this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
+
private def List<Type> getBaseIfcs(GeneratedType type) {
val List<Type> baseIfcs = new ArrayList();
for (ifc : type.implements) {
private def List<String> toListOfNames(Collection<Type> types) {
val List<String> names = new ArrayList
for (type : types) {
- names.add(type.fullyQualifiedName)
+ names.add(type.importedName)
}
return names
}
def private generateSetter(GeneratedProperty field) {
val returnType = field.returnType
if (returnType instanceof ParameterizedType) {
- if (Types.isListType(returnType)) {
- return generateListSetter(field, returnType.actualTypeArguments.get(0))
+ if (Types.isListType(returnType) || Types.isSetType(returnType)) {
+ val arguments = returnType.actualTypeArguments
+ if (arguments.isEmpty) {
+ return generateListSetter(field, Types.objectType)
+ }
+ return generateListSetter(field, arguments.get(0))
+ } else if (Types.isMapType(returnType)) {
+ return generateMapSetter(field, returnType.actualTypeArguments.get(1))
}
}
return generateSimpleSetter(field, returnType)
'''
+ def private generateMapSetter(GeneratedProperty field, Type actualType) '''
+ «val restrictions = restrictionsForSetter(actualType)»
+ «IF restrictions !== null»
+ «generateCheckers(field, restrictions, actualType)»
+ «ENDIF»
+ public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
+ «IF restrictions !== null»
+ if (values != null) {
+ for («actualType.importedName» value : values.values()) {
+ «checkArgument(field, restrictions, actualType, "value")»
+ }
+ }
+ «ENDIF»
+ this.«field.fieldName» = values;
+ return this;
+ }
+ '''
+
def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
«val restrictions = restrictionsForSetter(actualType)»
«IF restrictions !== null»
«IF augmentType !== null»
«val augmentTypeRef = augmentType.importedName»
- «val jlClassRef = CLASS.importedName»
- public «type.name» add«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
- if (augmentationValue == null) {
- return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
- }
-
- if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
- this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
+ «val hashMapRef = JU_HASHMAP.importedName»
+ /**
+ * Add an augmentation to this builder's product.
+ *
+ * @param augmentation augmentation to be added
+ * @return this builder
+ * @throws NullPointerException if {@code augmentation} is null
+ */
+ public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
+ if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
+ this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
}
- this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
+ this.«AUGMENTATION_FIELD».put(augmentation.«BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
return this;
}
- public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
- if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
+ /**
+ * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
+ * type, this method does nothing.
+ *
+ * @param augmentationType augmentation type to be removed
+ * @return this builder
+ */
+ public «type.name» removeAugmentation(«CLASS.importedName»<? extends «augmentTypeRef»> augmentationType) {
+ if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
this.«AUGMENTATION_FIELD».remove(augmentationType);
}
return this;
'''
private def createDescription(GeneratedType targetType) {
- val target = type.importedName
+ val target = targetType.importedName
return '''
Class that builds {@link «target»} instances. Overall design of the class is that of a
<a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
In general, this class is supposed to be used like this template:
<pre>
<code>
- «target» createTarget(int fooXyzzy, int barBaz) {
+ «target» create«target»(int fooXyzzy, int barBaz) {
return new «target»Builder()
.setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
.setBar(new BarBuilder().setBaz(barBaz).build())
invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
<li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
very localized</li>
- <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
+ <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
eliminated</li>
</ul>
@see «target»
- @see «Builder.importedName»
'''
}
}
private def generateAugmentation() '''
+ /**
+ * Return the specified augmentation, if it is present in this builder.
+ *
+ * @param <E$$> augmentation type
+ * @param augmentationType augmentation type class
+ * @return Augmentation object from this builder, or {@code null} if not present
+ * @throws «NPE.importedName» if {@code augmentType} is {@code null}
+ */
@«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
- return (E$$) «AUGMENTATION_FIELD».get(«CODEHELPERS.importedName».nonNullValue(augmentationType, "augmentationType"));
+ return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
}
'''
«ENDFOR»
'''
+ override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
+ «FOR field : props»
+ this.«field.fieldName» = base.«field.getterName»();
+ «ENDFOR»
+ '''
+
override protected generateCopyAugmentation(Type implType) {
- val augmentationHolderRef = AugmentationHolder.importedName
- val typeRef = targetType.importedName
- val hashMapRef = HashMap.importedName
+ val hashMapRef = JU_HASHMAP.importedName
val augmentTypeRef = augmentType.importedName
return '''
- if (base instanceof «augmentationHolderRef») {
- @SuppressWarnings("unchecked")
- «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
- if (!aug.isEmpty()) {
- this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
- }
+ «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
+ if (!aug.isEmpty()) {
+ this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
}
'''
}
-
- private static def hasNonDefaultMethods(GeneratedType type) {
- !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
- }
-
- private static def nonDefaultMethods(GeneratedType type) {
- type.methodDefinitions.filter([def | !def.isDefault])
- }
}