From: miroslav.kovac Date: Wed, 11 Mar 2020 15:42:29 +0000 (+0100) Subject: Specialize relative leafref types during instantiation X-Git-Tag: v7.0.3~2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=mdsal.git;a=commitdiff_plain;h=a86bc81d199978aeb0b5a08e52975973a4ea9e6a Specialize relative leafref types during instantiation leaf of relative leafref type declared in grouping could reference node outside of it. In such case target node of leafref depends on location where its origin grouping used. Before property from relative leafref was calculated from its origin grouping and this property were used at all GeneratedTypes produced from the grouping users. When leafref pointed outside of its origin grouping, Object for leaves, List for leaf-list were used as property type. Now type resolving is running from locations, where relative leafref is added by uses and its ancestors grouping do not add a node at the leafref path. JIRA: MDSAL-426 JIRA: MDSAL-533 Change-Id: I5004f0579778527511b4b028e00f7ab9c3051731 Signed-off-by: miroslav.kovac Signed-off-by: Ilya Igushev Signed-off-by: Robert Varga --- diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java index 99dee6fda3..e8484a4bb1 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java @@ -359,7 +359,8 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri final Class parentClass, final Map getterToLeafSchema) { final Map leaves = new HashMap<>(); for (final Method method : parentClass.getMethods()) { - if (method.getParameterCount() == 0) { + // Only consider non-bridge methods with no arguments + if (method.getParameterCount() == 0 && !method.isBridge()) { final DataSchemaNode schema = getterToLeafSchema.get(method.getName()); final ValueNodeCodecContext valueNode; diff --git a/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/impl/SpecializingLeafrefTest.java b/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/impl/SpecializingLeafrefTest.java new file mode 100644 index 0000000000..33ba3909b0 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/impl/SpecializingLeafrefTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.dom.codec.impl; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.opendaylight.yang.gen.v1.mdsal426.norev.BarCont; +import org.opendaylight.yang.gen.v1.mdsal426.norev.BarContBuilder; +import org.opendaylight.yang.gen.v1.mdsal426.norev.BooleanCont; +import org.opendaylight.yang.gen.v1.mdsal426.norev.BooleanContBuilder; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +public class SpecializingLeafrefTest extends AbstractBindingCodecTest { + private static final InstanceIdentifier BOOLEAN_CONT_II = InstanceIdentifier + .builder(BooleanCont.class).build(); + + private static final InstanceIdentifier BAR_CONT_II = InstanceIdentifier + .builder(BarCont.class).build(); + + @Test + public void specifiedBooleanLeafTest() { + final BooleanCont booleanCont = new BooleanContBuilder().setIsFoo(true).build(); + + final Map.Entry> res = codecContext + .toNormalizedNode(BOOLEAN_CONT_II, booleanCont); + + final BooleanCont booleanContBinding = (BooleanCont)codecContext + .fromNormalizedNode(res.getKey(), res.getValue()).getValue(); + + assertTrue(booleanContBinding.isIsFoo()); + } + + @Test + public void specifiedCommonLeafTest() { + final BarCont barCont = new BarContBuilder().setLeaf2("foo").build(); + + final Map.Entry> res = codecContext + .toNormalizedNode(BAR_CONT_II, barCont); + + final BarCont booleanContBinding = (BarCont)codecContext + .fromNormalizedNode(res.getKey(), res.getValue()).getValue(); + + assertEquals(booleanContBinding.getLeaf2(), "foo"); + } + + @Test + public void specifiedLeafListTest() { + final List testList = ImmutableList.of("test"); + final BarCont barCont = new BarContBuilder().setLeafList1(testList).build(); + + final Map.Entry> res = codecContext + .toNormalizedNode(BAR_CONT_II, barCont); + + final BarCont barContAfterConverting = (BarCont)codecContext + .fromNormalizedNode(res.getKey(), res.getValue()).getValue(); + + assertEquals(barContAfterConverting.getLeafList1(), testList); + } +} diff --git a/binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java b/binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java index 15e3f4b744..d3f95ebd13 100644 --- a/binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java +++ b/binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java @@ -40,8 +40,10 @@ import static org.opendaylight.mdsal.binding.model.util.Types.BOOLEAN; import static org.opendaylight.mdsal.binding.model.util.Types.STRING; import static org.opendaylight.mdsal.binding.model.util.Types.classType; import static org.opendaylight.mdsal.binding.model.util.Types.listTypeFor; +import static org.opendaylight.mdsal.binding.model.util.Types.listTypeWildcard; import static org.opendaylight.mdsal.binding.model.util.Types.listenableFutureTypeFor; import static org.opendaylight.mdsal.binding.model.util.Types.mapTypeFor; +import static org.opendaylight.mdsal.binding.model.util.Types.objectType; import static org.opendaylight.mdsal.binding.model.util.Types.primitiveBooleanType; import static org.opendaylight.mdsal.binding.model.util.Types.primitiveIntType; import static org.opendaylight.mdsal.binding.model.util.Types.primitiveVoidType; @@ -67,12 +69,14 @@ import java.util.Optional; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.binding.model.api.AccessModifier; +import org.opendaylight.mdsal.binding.model.api.AnnotationType; import org.opendaylight.mdsal.binding.model.api.Constant; import org.opendaylight.mdsal.binding.model.api.DefaultType; import org.opendaylight.mdsal.binding.model.api.Enumeration; 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.MethodSignature.ValueMechanics; import org.opendaylight.mdsal.binding.model.api.ParameterizedType; import org.opendaylight.mdsal.binding.model.api.Restrictions; @@ -142,6 +146,38 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; abstract class AbstractTypeGenerator { + private enum InheritedGetter { + /** + * There is no matching method present. + */ + NOT_PRESENT, + /** + * There is a matching method and its return type is resolved. + */ + RESOLVED, + /** + * There is a matching method and its return type is unresolved -- i.e. in case of a leafref pointing to outside + * of its parent grouping. + */ + UNRESOLVED; + + /** + * We are using {@code @Override} annotation to indicate specialization, hence we can differentiate between + * resolved and unresolved methods based on them. + * + * @param annotations Method annotations + * @return Either {@link #RESOLVED} or {@link #UNRESOLVED}. + */ + static InheritedGetter fromAnnotations(final List annotations) { + for (AnnotationType annotation : annotations) { + if (OVERRIDE_ANNOTATION.equals(annotation.getIdentifier())) { + return InheritedGetter.RESOLVED; + } + } + return InheritedGetter.UNRESOLVED; + } + } + private static final Logger LOG = LoggerFactory.getLogger(AbstractTypeGenerator.class); private static final Splitter COLON_SPLITTER = Splitter.on(':'); private static final JavaTypeName DEPRECATED_ANNOTATION = JavaTypeName.create(Deprecated.class); @@ -317,7 +353,7 @@ abstract class AbstractTypeGenerator { return genType; } - private void containerToGenType(final ModuleContext context, final GeneratedTypeBuilder parent, + private Type containerToGenType(final ModuleContext context, final GeneratedTypeBuilder parent, final Type baseInterface, final ContainerSchemaNode node, final boolean inGrouping) { final GeneratedTypeBuilder genType = processDataSchemaNode(context, baseInterface, node, inGrouping); if (genType != null) { @@ -326,9 +362,10 @@ abstract class AbstractTypeGenerator { actionsToGenType(context, genType, node, null, inGrouping); notificationsToGenType(context, genType, node, null, inGrouping); } + return genType; } - private void listToGenType(final ModuleContext context, final GeneratedTypeBuilder parent, + private GeneratedTypeBuilder listToGenType(final ModuleContext context, final GeneratedTypeBuilder parent, final Type baseInterface, final ListSchemaNode node, final boolean inGrouping) { final GeneratedTypeBuilder genType = processDataSchemaNode(context, baseInterface, node, inGrouping); if (genType != null) { @@ -373,6 +410,7 @@ abstract class AbstractTypeGenerator { typeBuildersToGenTypes(context, genType, keyTypeBuilder); } + return genType; } private void processUsesAugments(final DataNodeContainer node, final ModuleContext context, @@ -1113,7 +1151,7 @@ abstract class AbstractTypeGenerator { if (schemaNodes != null && parent != null) { final Type baseInterface = childOf == null ? DATA_OBJECT : childOf(childOf); for (final DataSchemaNode schemaNode : schemaNodes) { - if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) { + if (!schemaNode.isAugmenting()) { addSchemaNodeToBuilderAsMethod(context, schemaNode, parent, baseInterface, inGrouping); } } @@ -1121,6 +1159,92 @@ abstract class AbstractTypeGenerator { return parent; } + private void addSchemaNodeToBuilderAsMethod(final ModuleContext context, final DataSchemaNode schemaNode, + final GeneratedTypeBuilder parent, final Type baseInterface, final boolean inGrouping) { + if (!schemaNode.isAddedByUses()) { + addUnambiguousNodeToBuilderAsMethod(context, schemaNode, parent, baseInterface, inGrouping); + } else if (needGroupingMethodOverride(schemaNode, parent)) { + addLeafrefNodeToBuilderAsMethod(context, (TypedDataSchemaNode) schemaNode, parent, inGrouping); + } + } + + /** + * Determine whether a particular node, added from a grouping, needs to be reflected as a method. This method + * performs a check for {@link TypedDataSchemaNode} and defers to + * {@link #needGroupingMethodOverride(TypedDataSchemaNode, GeneratedTypeBuilder)}. + * + * @param parent {@code GeneratedType} where method should be defined + * @param child node from which method should be defined + * @return True if an override method is needed + */ + private static boolean needGroupingMethodOverride(final DataSchemaNode child, final GeneratedTypeBuilder parent) { + return child instanceof TypedDataSchemaNode && needGroupingMethodOverride((TypedDataSchemaNode) child, parent); + } + + /** + * Determine whether a particular {@link TypedDataSchemaNode}, added from a grouping, needs to be reflected as a + * method. + * + *

+ * This check would be super easy were it not for relative leafrefs in groupings. These can legally point outside of + * the grouping -- which means we cannot inherently cannot determine their type, as they are polymorphic. + * + * @param parent {@code GeneratedType} where method should be defined + * @param child node from which method should be defined + * @return True if an override method is needed + */ + private static boolean needGroupingMethodOverride(final TypedDataSchemaNode child, + final GeneratedTypeBuilder parent) { + // This is a child added through uses and it is is data-bearing, i.e. leaf or leaf-list. Under normal + // circumstances we would not bother, but if the target type is a leafref we have more checks to do. + return isRelativeLeafref(child.getType()) && needMethodDefinition(child.getQName().getLocalName(), parent); + } + + private static boolean needMethodDefinition(final String localName, final GeneratedTypeBuilder parent) { + for (Type implementsType : parent.getImplementsTypes()) { + if (implementsType instanceof GeneratedType) { + final InheritedGetter precision = findInheritedGetter(localName, (GeneratedType) implementsType); + switch (precision) { + case RESOLVED: + return false; + case UNRESOLVED: + return true; + default: + // No-op + } + } + } + throw new IllegalStateException(localName + " should be present in " + parent + + " or in one of its ancestors as a getter"); + } + + private static InheritedGetter findInheritedGetter(final String localName, final GeneratedType impl) { + return findInheritedGetter(impl, BindingMapping.getGetterMethodName(localName)); + } + + private static InheritedGetter findInheritedGetter(final GeneratedType type, final String getter) { + for (MethodSignature method : type.getMethodDefinitions()) { + if (getter.equals(method.getName())) { + return InheritedGetter.fromAnnotations(method.getAnnotations()); + } + } + + // Try to find the method in other interfaces we implement + for (Type implementsType : type.getImplements()) { + if (implementsType instanceof GeneratedType) { + final InheritedGetter found = findInheritedGetter((GeneratedType) implementsType, getter); + if (found != InheritedGetter.NOT_PRESENT) { + return found; + } + } + } + return InheritedGetter.NOT_PRESENT; + } + + private static boolean isRelativeLeafref(final TypeDefinition> type) { + return type instanceof LeafrefTypeDefinition && !((LeafrefTypeDefinition) type).getPathStatement().isAbsolute(); + } + /** * Adds the methods to typeBuilder what represents subnodes of node for which typeBuilder * was created. @@ -1150,35 +1274,50 @@ abstract class AbstractTypeGenerator { } /** - * Adds to typeBuilder a method which is derived from schemaNode. + * Adds to {@code typeBuilder} a method which is derived from {@code schemaNode}. * * @param node data schema node which is added to typeBuilder as a method * @param typeBuilder generated type builder to which is schemaNode added as a method. * @param childOf parent type * @param module current module */ - private void addSchemaNodeToBuilderAsMethod(final ModuleContext context, final DataSchemaNode node, - final GeneratedTypeBuilder typeBuilder, final Type baseInterface, final boolean inGrouping) { + private void addLeafrefNodeToBuilderAsMethod(final ModuleContext context, final TypedDataSchemaNode node, + final GeneratedTypeBuilder typeBuilder, final boolean inGrouping) { if (node != null && typeBuilder != null) { if (node instanceof LeafSchemaNode) { - resolveLeafSchemaNodeAsMethod(typeBuilder, (LeafSchemaNode) node, context, inGrouping); + resolveLeafLeafrefNodeAsMethod(typeBuilder, (LeafSchemaNode) node, context, inGrouping); } else if (node instanceof LeafListSchemaNode) { - resolveLeafListSchemaNode(typeBuilder, (LeafListSchemaNode) node, context, inGrouping); - } else if (node instanceof ContainerSchemaNode) { - containerToGenType(context, typeBuilder, baseInterface, (ContainerSchemaNode) node, inGrouping); - } else if (node instanceof ListSchemaNode) { - listToGenType(context, typeBuilder, baseInterface, (ListSchemaNode) node, inGrouping); - } else if (node instanceof ChoiceSchemaNode) { - choiceToGeneratedType(context, typeBuilder, (ChoiceSchemaNode) node, inGrouping); - } else if (node instanceof AnyxmlSchemaNode || node instanceof AnydataSchemaNode) { - opaqueToGeneratedType(context, typeBuilder, node); + resolveLeafListLeafrefNode(typeBuilder, (LeafListSchemaNode) node, context, inGrouping); } else { - LOG.debug("Unable to add schema node {} as method in {}: unsupported type of node.", node.getClass(), - typeBuilder.getFullyQualifiedName()); + logUnableToAddNodeAsMethod(node, typeBuilder); } } } + private void addUnambiguousNodeToBuilderAsMethod(final ModuleContext context, final DataSchemaNode node, + final GeneratedTypeBuilder typeBuilder, final Type baseInterface, final boolean inGrouping) { + if (node instanceof LeafSchemaNode) { + resolveUnambiguousLeafNodeAsMethod(typeBuilder, (LeafSchemaNode) node, context, inGrouping); + } else if (node instanceof LeafListSchemaNode) { + resolveUnambiguousLeafListNode(typeBuilder, (LeafListSchemaNode) node, context, inGrouping); + } else if (node instanceof ContainerSchemaNode) { + containerToGenType(context, typeBuilder, baseInterface, (ContainerSchemaNode) node, inGrouping); + } else if (node instanceof ListSchemaNode) { + listToGenType(context, typeBuilder, baseInterface, (ListSchemaNode) node, inGrouping); + } else if (node instanceof ChoiceSchemaNode) { + choiceToGeneratedType(context, typeBuilder, (ChoiceSchemaNode) node, inGrouping); + } else if (node instanceof AnyxmlSchemaNode || node instanceof AnydataSchemaNode) { + opaqueToGeneratedType(context, typeBuilder, node); + } else { + logUnableToAddNodeAsMethod(node, typeBuilder); + } + } + + private static void logUnableToAddNodeAsMethod(final DataSchemaNode node, final GeneratedTypeBuilder typeBuilder) { + LOG.debug("Unable to add schema node {} as method in {}: unsupported type of node.", node.getClass(), + typeBuilder.getFullyQualifiedName()); + } + /** * Converts choiceNode to the list of generated types for choice and its cases. The package names * for choice and for its cases are created as concatenation of the module package (basePackageName) @@ -1413,26 +1552,44 @@ abstract class AbstractTypeGenerator { } } + private Type resolveLeafLeafrefNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf, + final ModuleContext context, final boolean inGrouping) { + final Module parentModule = findParentModule(schemaContext, leaf); + final Type returnType = resolveReturnType(typeBuilder, leaf, context, parentModule, inGrouping); + if (returnType != null && isTypeSpecified(returnType)) { + processContextRefExtension(leaf, constructOverrideGetter(typeBuilder, returnType, leaf), parentModule); + } + return returnType; + } + /** - * Converts leaf to the getter method which is added to typeBuilder. + * Converts {@code leafList} to the getter method which is added to {@code typeBuilder}. * - * @param typeBuilder generated type builder to which is added getter method as leaf mapping - * @param leaf leaf schema node which is mapped as getter method which is added to typeBuilder - * @param module Module in which type was defined - * @return boolean value - *

    - *
  • false - if leaf or typeBuilder are - * null
  • - *
  • true - in other cases
  • - *
+ * @param context module in which type was defined + * @param typeBuilder generated type builder to which is added getter method as {@code leafList} mapping + * @param leafList leaf-list schema node which is mapped as getter method which is added to {@code typeBuilder} */ - private Type resolveLeafSchemaNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf, + private void resolveLeafListNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode leafList, final ModuleContext context, final boolean inGrouping) { - if (leaf == null || typeBuilder == null || leaf.isAddedByUses()) { - return null; + if (!leafList.isAddedByUses()) { + resolveUnambiguousLeafListNode(typeBuilder, leafList, context, inGrouping); + } else if (needGroupingMethodOverride(leafList, typeBuilder)) { + resolveLeafListLeafrefNode(typeBuilder, leafList, context, inGrouping); } + } + private Type resolveUnambiguousLeafNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf, + final ModuleContext context, final boolean inGrouping) { final Module parentModule = findParentModule(schemaContext, leaf); + final Type returnType = resolveReturnType(typeBuilder, leaf, context, parentModule, inGrouping); + if (returnType != null) { + processContextRefExtension(leaf, constructGetter(typeBuilder, returnType, leaf), parentModule); + } + return returnType; + } + + private Type resolveReturnType(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf, + final ModuleContext context, final Module parentModule, final boolean inGrouping) { Type returnType = null; final TypeDefinition typeDef = CompatUtils.compatType(leaf); @@ -1488,11 +1645,13 @@ abstract class AbstractTypeGenerator { typeProvider.putReferencedType(leaf.getPath(), returnType); } - final MethodSignatureBuilder getter = constructGetter(typeBuilder, returnType, leaf); - processContextRefExtension(leaf, getter, parentModule); return returnType; } + private static boolean isTypeSpecified(final Type type) { + return !type.equals(objectType()); + } + private static TypeDefinition getBaseOrDeclaredType(final TypeDefinition typeDef) { // Returns DerivedType in case of new parser. final TypeDefinition baseType = typeDef.getBaseType(); @@ -1615,42 +1774,53 @@ abstract class AbstractTypeGenerator { return true; } + private void resolveLeafListLeafrefNode(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node, + final ModuleContext context, final boolean inGrouping) { + final Type returnType = resolveLeafListItemsType(typeBuilder, node, context, inGrouping, + findParentModule(schemaContext, node)); + if (isTypeSpecified(returnType)) { + constructOverrideGetter(typeBuilder, listTypeFor(returnType), node); + } + } + /** * Converts node leaf list schema node to getter method of typeBuilder. * + * @param context module * @param typeBuilder generated type builder to which is node added as getter method * @param node leaf list schema node which is added to typeBuilder as getter method - * @param module module - * @return boolean value - *
    - *
  • true - if node, typeBuilder, - * nodeName equal null or node is added by uses
  • - *
  • false - other cases
  • - *
*/ - private boolean resolveLeafListSchemaNode(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node, + private Type resolveUnambiguousLeafListNode(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node, final ModuleContext context, final boolean inGrouping) { - if (node == null || typeBuilder == null || node.isAddedByUses()) { - return false; + final Module parentModule = findParentModule(schemaContext, node); + final Type listItemsType = resolveLeafListItemsType(typeBuilder, node, context, inGrouping, parentModule); + final Type returnType; + if (listItemsType.equals(objectType())) { + returnType = listTypeWildcard(); + } else { + returnType = listTypeFor(listItemsType); } - final QName nodeName = node.getQName(); + constructGetter(typeBuilder, returnType, node); - final TypeDefinition typeDef = node.getType(); - final Module parentModule = findParentModule(schemaContext, node); + return returnType; + } - Type returnType = null; + private Type resolveLeafListItemsType(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node, + final ModuleContext context, final boolean inGrouping, final Module parentModule) { + final Type returnType; + final TypeDefinition> typeDef = node.getType(); if (typeDef.getBaseType() == null) { if (typeDef instanceof EnumTypeDefinition) { final EnumTypeDefinition enumTypeDef = (EnumTypeDefinition) typeDef; - returnType = resolveInnerEnumFromTypeDefinition(enumTypeDef, nodeName, typeBuilder, context); + returnType = resolveInnerEnumFromTypeDefinition(enumTypeDef, node.getQName(), typeBuilder, context); typeProvider.putReferencedType(node.getPath(), returnType); } else if (typeDef instanceof UnionTypeDefinition) { - final UnionTypeDefinition unionDef = (UnionTypeDefinition)typeDef; + final UnionTypeDefinition unionDef = (UnionTypeDefinition) typeDef; returnType = addTOToTypeBuilder(unionDef, typeBuilder, node, parentModule); } else if (typeDef instanceof BitsTypeDefinition) { - final GeneratedTOBuilder genTOBuilder = addTOToTypeBuilder((BitsTypeDefinition)typeDef, typeBuilder, - node, parentModule); + final GeneratedTOBuilder genTOBuilder = addTOToTypeBuilder((BitsTypeDefinition) typeDef, typeBuilder, + node, parentModule); returnType = genTOBuilder.build(); } else { final Restrictions restrictions = BindingGeneratorUtil.getRestrictions(typeDef); @@ -1662,9 +1832,7 @@ abstract class AbstractTypeGenerator { returnType = typeProvider.javaTypeForSchemaDefinitionType(typeDef, node, restrictions, inGrouping); addPatternConstant(typeBuilder, node.getQName().getLocalName(), restrictions.getPatternConstraints()); } - - constructGetter(typeBuilder, listTypeFor(returnType), node); - return true; + return returnType; } private Type createReturnTypeForUnion(final GeneratedTOBuilder genTOBuilder, final UnionTypeDefinition typeDef, @@ -1829,6 +1997,13 @@ abstract class AbstractTypeGenerator { return getMethod; } + private MethodSignatureBuilder constructOverrideGetter(final GeneratedTypeBuilder interfaceBuilder, + final Type returnType, final SchemaNode node) { + final MethodSignatureBuilder getter = constructGetter(interfaceBuilder, returnType, node); + getter.addAnnotation(OVERRIDE_ANNOTATION); + return getter; + } + private static void constructNonnull(final GeneratedTypeBuilder interfaceBuilder, final Type returnType, final ListSchemaNode node) { final MethodSignatureBuilder getMethod = interfaceBuilder.addMethod( @@ -1863,7 +2038,15 @@ abstract class AbstractTypeGenerator { if (schemaNode instanceof LeafSchemaNode) { final LeafSchemaNode leaf = (LeafSchemaNode) schemaNode; final String leafName = leaf.getQName().getLocalName(); - Type type = resolveLeafSchemaNodeAsMethod(typeBuilder, leaf, context, inGrouping); + final Type type; + if (!schemaNode.isAddedByUses()) { + type = resolveUnambiguousLeafNodeAsMethod(typeBuilder, leaf, context, inGrouping); + } else if (needGroupingMethodOverride(leaf, typeBuilder)) { + type = resolveLeafLeafrefNodeAsMethod(typeBuilder, leaf, context, inGrouping); + } else { + type = null; + } + if (listKeys.contains(leafName)) { if (type == null) { resolveLeafSchemaNodeAsProperty(genTOBuilder, leaf, true); @@ -1871,12 +2054,12 @@ abstract class AbstractTypeGenerator { resolveLeafSchemaNodeAsProperty(genTOBuilder, leaf, type, true); } } + } else if (schemaNode instanceof LeafListSchemaNode) { + resolveLeafListNodeAsMethod(typeBuilder, (LeafListSchemaNode) schemaNode, context, inGrouping); } else if (!schemaNode.isAddedByUses()) { - if (schemaNode instanceof LeafListSchemaNode) { - resolveLeafListSchemaNode(typeBuilder, (LeafListSchemaNode) schemaNode, context, inGrouping); - } else if (schemaNode instanceof ContainerSchemaNode) { - containerToGenType(context, typeBuilder, childOf(typeBuilder), - (ContainerSchemaNode) schemaNode, inGrouping); + if (schemaNode instanceof ContainerSchemaNode) { + containerToGenType(context, typeBuilder, childOf(typeBuilder), (ContainerSchemaNode) schemaNode, + inGrouping); } else if (schemaNode instanceof ChoiceSchemaNode) { choiceToGeneratedType(context, typeBuilder, (ChoiceSchemaNode) schemaNode, inGrouping); } else if (schemaNode instanceof ListSchemaNode) { diff --git a/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/Types.java b/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/Types.java index deae83c0e8..97835bd02a 100644 --- a/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/Types.java +++ b/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/Types.java @@ -51,10 +51,10 @@ public final class Types { CacheBuilder.newBuilder().weakKeys().build(TYPE_LOADER); public static final @NonNull ConcreteType BOOLEAN = typeForClass(Boolean.class); - public static final @NonNull ConcreteType STRING = typeForClass(String.class); - public static final @NonNull ConcreteType VOID = typeForClass(Void.class); public static final @NonNull ConcreteType BYTE_ARRAY = typeForClass(byte[].class); public static final @NonNull ConcreteType CLASS = typeForClass(Class.class); + public static final @NonNull ConcreteType STRING = typeForClass(String.class); + public static final @NonNull ConcreteType VOID = typeForClass(Void.class); private static final @NonNull ConcreteType BUILDER = typeForClass(Builder.class); private static final @NonNull ConcreteType LIST_TYPE = typeForClass(List.class); @@ -66,6 +66,7 @@ public final class Types { private static final @NonNull ConcreteType PRIMITIVE_VOID = typeForClass(void.class); private static final @NonNull ConcreteType SERIALIZABLE = typeForClass(Serializable.class); private static final @NonNull ConcreteType SET_TYPE = typeForClass(Set.class); + private static final @NonNull ParameterizedType LIST_TYPE_WILDCARD = parameterizedTypeFor(LIST_TYPE); /** * It is not desirable to create instance of this class. @@ -206,6 +207,15 @@ public final class Types { return parameterizedTypeFor(LIST_TYPE, valueType); } + /** + * Returns an instance of {@link ParameterizedType} describing the typed {@link List}<?>. + * + * @return Description of type instance of List + */ + public static @NonNull ParameterizedType listTypeWildcard() { + return LIST_TYPE_WILDCARD; + } + public static boolean isListType(final ParameterizedType type) { return LIST_TYPE.equals(type.getRawType()); } @@ -258,6 +268,20 @@ public final class Types { return new WildcardTypeImpl(identifier); } + public static boolean strictTypeEquals(final Type type1, final Type type2) { + if (!type1.equals(type2)) { + return false; + } + if (type1 instanceof ParameterizedType) { + if (type2 instanceof ParameterizedType) { + return Arrays.equals(((ParameterizedType) type1).getActualTypeArguments(), + ((ParameterizedType) type2).getActualTypeArguments()); + } + return false; + } + return !(type2 instanceof ParameterizedType); + } + public static @Nullable String getOuterClassName(final Type valueType) { return valueType.getIdentifier().immediatelyEnclosingClass().map(Object::toString).orElse(null); } diff --git a/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/generated/type/builder/AbstractGeneratedType.java b/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/generated/type/builder/AbstractGeneratedType.java index 927751f110..19771709e0 100644 --- a/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/generated/type/builder/AbstractGeneratedType.java +++ b/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/generated/type/builder/AbstractGeneratedType.java @@ -9,8 +9,10 @@ package org.opendaylight.mdsal.binding.model.util.generated.type.builder; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.opendaylight.mdsal.binding.model.api.AbstractBaseType; import org.opendaylight.mdsal.binding.model.api.AnnotationType; import org.opendaylight.mdsal.binding.model.api.Constant; @@ -90,6 +92,17 @@ abstract class AbstractGeneratedType extends AbstractBaseType implements Generat } } + protected static Set makeUnmodifiable(final Set set) { + switch (set.size()) { + case 0: + return Collections.emptySet(); + case 1: + return Collections.singleton(set.iterator().next()); + default: + return Collections.unmodifiableSet(set); + } + } + private static List toUnmodifiableEnclosedTypes( final List enclosedGenTypeBuilders, final List enclosedGenTOBuilders) { @@ -127,6 +140,14 @@ abstract class AbstractGeneratedType extends AbstractBaseType implements Generat return makeUnmodifiable(methods); } + protected final Set toUnmodifiableMethods(final Set getters) { + final Set methods = new HashSet<>(getters.size()); + for (final MethodSignatureBuilder methodBuilder : getters) { + methods.add(methodBuilder.toInstance(this)); + } + return makeUnmodifiable(methods); + } + protected final List toUnmodifiableEnumerations(final List enumBuilders) { final List enums = new ArrayList<>(enumBuilders.size()); for (final EnumBuilder enumBuilder : enumBuilders) { diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractBuilderTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractBuilderTemplate.xtend index 85147c869b..5dcce2a4c9 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractBuilderTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractBuilderTemplate.xtend @@ -172,4 +172,12 @@ abstract class AbstractBuilderTemplate extends BaseTemplate { } return null } + + package static def hasNonDefaultMethods(GeneratedType type) { + !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault]) + } + + package static def nonDefaultMethods(GeneratedType type) { + type.methodDefinitions.filter([def | !def.isDefault]) + } } diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend index f805ef953b..224d212df5 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend @@ -102,18 +102,6 @@ abstract class BaseTemplate extends JavaFileTemplate { "_" + property.name } - final protected static def propertyNameFromGetter(MethodSignature getter) { - var String prefix; - if (BindingMapping.isGetterMethodName(getter.name)) { - prefix = BindingMapping.GETTER_PREFIX - } else if (BindingMapping.isNonnullMethodName(getter.name)) { - prefix = BindingMapping.NONNULL_PREFIX - } else { - throw new IllegalArgumentException(getter + " is not a getter") - } - return getter.name.substring(prefix.length).toFirstLower; - } - /** * Template method which generates the getter method for field * diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderImplTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderImplTemplate.xtend index 040e392cc5..25c07c25c6 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderImplTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderImplTemplate.xtend @@ -15,9 +15,11 @@ import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_ import java.util.Collection import java.util.List +import java.util.Optional import org.opendaylight.mdsal.binding.model.api.AnnotationType import org.opendaylight.mdsal.binding.model.api.GeneratedProperty import org.opendaylight.mdsal.binding.model.api.GeneratedType +import org.opendaylight.mdsal.binding.model.api.MethodSignature import org.opendaylight.mdsal.binding.model.api.MethodSignature.ValueMechanics import org.opendaylight.mdsal.binding.model.api.Type import org.opendaylight.mdsal.binding.model.util.Types @@ -25,12 +27,12 @@ import org.opendaylight.mdsal.binding.spec.naming.BindingMapping import org.opendaylight.yangtools.yang.binding.AbstractAugmentable class BuilderImplTemplate extends AbstractBuilderTemplate { - val Type builderType; + val BuilderTemplate builder; new(BuilderTemplate builder, GeneratedType type) { super(builder.javaType.getEnclosedType(type.identifier), type, builder.targetType, builder.properties, builder.augmentType, builder.keyType) - this.builderType = builder.type + this.builder = builder } override body() ''' @@ -44,9 +46,9 @@ class BuilderImplTemplate extends AbstractBuilderTemplate { «generateFields(true)» - «generateCopyConstructor(builderType, type)» + «generateCopyConstructor(builder.type, type)» - «generateGetters(true)» + «generateGetters()» «generateHashCode()» @@ -60,6 +62,67 @@ class BuilderImplTemplate extends AbstractBuilderTemplate { return generateAnnotation(ann) } + def private generateGetters() ''' + «IF keyType !== null» + @«OVERRIDE.importedName» + public «keyType.importedName» «BindingMapping.IDENTIFIABLE_KEY_NAME»() { + return key; + } + + «ENDIF» + «IF !properties.empty» + «FOR field : properties SEPARATOR '\n'» + «field.getterMethod» + «ENDFOR» + «ENDIF» + ''' + + private static def Optional findGetter(GeneratedType implType, String getterName) { + val getter = getterByName(implType.nonDefaultMethods, getterName); + if (getter.isPresent) { + return getter; + } + for (ifc : implType.implements) { + if (ifc instanceof GeneratedType) { + val getterImpl = findGetter(ifc, getterName) + if (getterImpl.isPresent) { + return (getterImpl) + } + } + } + return Optional.empty + } + + override getterMethod(GeneratedProperty field) ''' + @«OVERRIDE.importedName» + public «field.returnType.importedName» «field.getterMethodName»() { + «val fieldName = field.fieldName» + «IF field.returnType.name.endsWith("[]")» + return «fieldName» == null ? null : «fieldName».clone(); + «ELSE» + return «fieldName»; + «ENDIF» + } + ''' + + package def findGetter(String getterName) { + val ownGetter = getterByName(type.nonDefaultMethods, getterName); + if (ownGetter.isPresent) { + return ownGetter.get; + } + for (ifc : type.implements) { + if (ifc instanceof GeneratedType) { + val getter = findGetter(ifc, getterName) + if (getter.isPresent) { + return (getter.get) + } + } + } + throw new IllegalStateException( + String.format("%s should be present in %s type or in one of its ancestors as getter", + getterName.propertyNameFromGetter, type)); + } + /** * Template method which generates the method hashCode(). * diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend index bd844616df..8bfa8817a3 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend @@ -14,6 +14,7 @@ import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTA import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME import com.google.common.collect.ImmutableList +import com.google.common.collect.Sets import java.util.ArrayList import java.util.Collection import java.util.HashSet @@ -25,12 +26,14 @@ import org.opendaylight.mdsal.binding.model.api.GeneratedProperty 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.spec.naming.BindingMapping import org.opendaylight.yangtools.concepts.Builder +import com.google.common.collect.ImmutableSet /** * Template for generating JAVA builder classes. @@ -43,12 +46,15 @@ class BuilderTemplate extends AbstractBuilderTemplate { static val BUILDER = JavaTypeName.create(Builder) + val BuilderImplTemplate implTemplate + /** * Constructs new instance of this class. * @throws IllegalArgumentException if genType equals null */ new(GeneratedType genType, GeneratedType targetType, Type keyType) { super(genType, targetType, keyType) + implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0)) } override isLocalInnerClass(JavaTypeName name) { @@ -93,7 +99,7 @@ class BuilderTemplate extends AbstractBuilderTemplate { return new «type.enclosedTypes.get(0).importedName»(this); } - «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body» + «implTemplate.body» } ''' @@ -111,8 +117,9 @@ class BuilderTemplate extends AbstractBuilderTemplate { def private generateConstructorsFromIfcs() ''' public «type.name»() { } + «IF (!(targetType instanceof GeneratedTransferObject))» - «FOR impl : targetType.implements» + «FOR impl : targetType.implements SEPARATOR "\n"» «generateConstructorFromIfc(impl)» «ENDFOR» «ENDIF» @@ -139,15 +146,41 @@ class BuilderTemplate extends AbstractBuilderTemplate { «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 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 getSpecifiedGetters(GeneratedType type) { + val ImmutableSet.Builder 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. */ @@ -177,7 +210,7 @@ class BuilderTemplate extends AbstractBuilderTemplate { * * * @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 */ ''' @@ -208,13 +241,28 @@ class BuilderTemplate extends AbstractBuilderTemplate { «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.fullyQualifiedName»)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 (Types.isListType(ownGetterType)) { + val itemType = (ownGetterType as ParameterizedType).actualTypeArguments.get(0) + return ''' + this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)''' + } + return ''' + this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetter.returnType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)''' + } + private def List getBaseIfcs(GeneratedType type) { val List baseIfcs = new ArrayList(); for (ifc : type.implements) { @@ -508,12 +556,4 @@ class BuilderTemplate extends AbstractBuilderTemplate { } ''' } - - 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]) - } } diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend index 6687c9efb1..b778c9e45c 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend @@ -10,7 +10,8 @@ package org.opendaylight.mdsal.binding.java.api.generator import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.getGetterMethodForNonnull import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isGetterMethodName import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isNonnullMethodName -import static org.opendaylight.mdsal.binding.model.util.Types.STRING; +import static org.opendaylight.mdsal.binding.model.util.Types.BOOLEAN +import static org.opendaylight.mdsal.binding.model.util.Types.STRING import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_EQUALS_NAME import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_HASHCODE_NAME @@ -107,6 +108,17 @@ class InterfaceTemplate extends BaseTemplate { «ENDIF» ''' + def private generateAccessorAnnotations(MethodSignature method) ''' + «val annotations = method.annotations» + «IF annotations !== null && !annotations.empty» + «FOR annotation : annotations» + «IF method.returnType != BOOLEAN || !(annotation.identifier == OVERRIDE)» + «annotation.generateAnnotation» + «ENDIF» + «ENDFOR» + «ENDIF» + ''' + /** * Template method which generates the interface name declaration. * @@ -245,7 +257,7 @@ class InterfaceTemplate extends BaseTemplate { def private generateAccessorMethod(MethodSignature method) { return ''' «accessorJavadoc(method, "{@code null}")» - «method.annotations.generateAnnotations» + «method.generateAccessorAnnotations» «method.returnType.nullableType» «method.name»(); ''' } diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/JavaFileTemplate.java b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/JavaFileTemplate.java index bef1e9edd9..6997b4d3f7 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/JavaFileTemplate.java +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/JavaFileTemplate.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.xtext.xbase.lib.StringExtensions; +import org.opendaylight.mdsal.binding.model.api.AnnotationType; import org.opendaylight.mdsal.binding.model.api.ConcreteType; import org.opendaylight.mdsal.binding.model.api.DefaultType; import org.opendaylight.mdsal.binding.model.api.GeneratedProperty; @@ -283,7 +284,7 @@ class JavaFileTemplate { for (Type implementedIfc : implementedIfcs) { if (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject)) { final GeneratedType ifc = (GeneratedType) implementedIfc; - methods.addAll(ifc.getMethodDefinitions()); + addImplMethods(methods, ifc); final ParameterizedType t = collectImplementedMethods(type, methods, ifc.getImplements()); if (t != null && augmentType == null) { @@ -297,6 +298,69 @@ class JavaFileTemplate { return augmentType; } + private static void addImplMethods(final Set methods, final GeneratedType implType) { + for (final MethodSignature implMethod : implType.getMethodDefinitions()) { + if (hasOverrideAnnotation(implMethod)) { + methods.add(implMethod); + } else { + final String implMethodName = implMethod.getName(); + if (BindingMapping.isGetterMethodName(implMethodName) + && getterByName(methods, implMethodName).isEmpty()) { + + methods.add(implMethod); + } + } + } + } + + protected static Optional getterByName(final Iterable methods, + final String implMethodName) { + for (MethodSignature method : methods) { + final String methodName = method.getName(); + if (BindingMapping.isGetterMethodName(methodName)) { + if (isSameProperty(method.getName(), implMethodName)) { + return Optional.of(method); + } + } + } + return Optional.empty(); + } + + protected static String propertyNameFromGetter(final MethodSignature getter) { + return propertyNameFromGetter(getter.getName()); + } + + protected static String propertyNameFromGetter(final String getterName) { + final String prefix; + if (BindingMapping.isGetterMethodName(getterName)) { + prefix = BindingMapping.GETTER_PREFIX; + } else if (BindingMapping.isNonnullMethodName(getterName)) { + prefix = BindingMapping.NONNULL_PREFIX; + } else { + throw new IllegalArgumentException(getterName + " is not a getter"); + } + return StringExtensions.toFirstLower(getterName.substring(prefix.length())); + } + + /** + * Check whether specified method has an attached annotation which corresponds to {@code @Override}. + * + * @param method Method to examine + * @return True if there is an override annotation + */ + static boolean hasOverrideAnnotation(final MethodSignature method) { + for (final AnnotationType annotation : method.getAnnotations()) { + if (OVERRIDE.equals(annotation.getIdentifier())) { + return true; + } + } + return false; + } + + private static boolean isSameProperty(final String getterName1, final String getterName2) { + return propertyNameFromGetter(getterName1).equals(propertyNameFromGetter(getterName2)); + } + /** * Creates set of generated property instances from getter methods. * diff --git a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java index e64c018e99..3e9b7b6cdb 100644 --- a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java +++ b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; @@ -341,6 +342,7 @@ public class BuilderGeneratorTest { final MethodSignature methSign = mock(MethodSignature.class); doReturn(methodeName).when(methSign).getName(); final Type methType = mock(Type.class); + when(methType.getFullyQualifiedName()).thenCallRealMethod(); doReturn(TYPE_NAME).when(methType).getIdentifier(); doReturn(TEST).when(methType).getName(); doReturn(methType).when(methSign).getReturnType(); diff --git a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/CompilationTest.java b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/CompilationTest.java index 83b14b0470..44d0b41ed9 100644 --- a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/CompilationTest.java +++ b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/CompilationTest.java @@ -685,6 +685,15 @@ public class CompilationTest extends BaseCompilationTest { CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir); } + @Test + public void testMdsal426() throws Exception { + final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal426"); + final File compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal426"); + generateTestSources("/compilation/mdsal426", sourcesOutputDir); + CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir); + CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir); + } + @Test public void testMdsal529() throws Exception { final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal529"); @@ -703,6 +712,14 @@ public class CompilationTest extends BaseCompilationTest { CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir); } + public void testMdsal533() throws Exception { + final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal533"); + final File compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal533"); + generateTestSources("/compilation/mdsal533", sourcesOutputDir); + CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir); + CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir); + } + private static void testReturnTypeIdentityref(final Class clazz, final String methodName, final String returnTypeStr) throws NoSuchMethodException { Method method = clazz.getMethod(methodName); diff --git a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/FileSearchUtil.java b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/FileSearchUtil.java index 599c712fe5..b57dfd5472 100644 --- a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/FileSearchUtil.java +++ b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/FileSearchUtil.java @@ -7,6 +7,9 @@ */ package org.opendaylight.mdsal.binding.java.api.generator.test; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -24,20 +27,18 @@ final class FileSearchUtil { } static void assertFileContains(final File file, final String searchText) throws IOException { - assertFileContains(file, Files.readString(file.toPath()), searchText); + assertFileContains(Files.readString(file.toPath()), searchText); } - static void assertFileContains(final File file, final String fileContent, final String searchText) { - if (!fileContent.contains(searchText)) { - throw new AssertionError("File " + file + " does not contain '" + searchText + "'"); - } + static void assertFileContains(final String fileContent, final String searchText) { + assertThat(fileContent, containsString(searchText)); } static void assertFileContainsConsecutiveLines(final File file, final String fileContent, final String ... lines) { for (final String line : lines) { - assertFileContains(file, fileContent, line); + assertFileContains(fileContent, line); } - assertFileContains(file, fileContent, String.join(LS, lines)); + assertFileContains(fileContent, String.join(LS, lines)); } static Map getFiles(final File path) { diff --git a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/SpecializingLeafrefTest.java b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/SpecializingLeafrefTest.java new file mode 100644 index 0000000000..b98b0b3136 --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/SpecializingLeafrefTest.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2020 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.java.api.generator.test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.DOUBLE_TAB; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.TAB; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.TRIPLE_TAB; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.doubleTab; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.getFiles; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.tab; +import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.tripleTab; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.mdsal.binding.model.api.GeneratedType; +import org.opendaylight.mdsal.binding.model.api.MethodSignature; +import org.opendaylight.mdsal.binding.model.api.Type; +import org.opendaylight.mdsal.binding.model.util.Types; + +public class SpecializingLeafrefTest extends BaseCompilationTest { + private static final Type LIST_STRING_TYPE = Types.listTypeFor(Types.STRING); + + public static final String BAR_CONT = "BarCont"; + public static final String BOOLEAN_CONT = "BooleanCont"; + + private static final String BAR_LST = "BarLst"; + private static final String BAZ_GRP = "BazGrp"; + private static final String FOO_GRP = "FooGrp"; + private static final String RESOLVED_LEAF_GRP = "ResolvedLeafGrp"; + private static final String RESOLVED_LEAFLIST_GRP = "ResolvedLeafListGrp"; + private static final String TRANSITIVE_GROUP = "TransitiveGroup"; + private static final String UNRESOLVED_GROUPING = "UnresolvedGrouping"; + + private static final String GET_LEAF1_NAME = "getLeaf1"; + private static final String GET_LEAFLIST1_NAME = "getLeafList1"; + private static final String IS_LEAF1_NAME = "isLeaf1"; + + private static final String GET_LEAF1_TYPE_OBJECT = " Object getLeaf1();"; + private static final String GET_LEAF1_TYPE_STRING = " String getLeaf1();"; + private static final String GET_LEAFLIST1_WILDCARD = " @Nullable List getLeafList1();"; + private static final String GET_LEAFLIST1_STRING = " @Nullable List getLeafList1();"; + private static final String GET_LEAFLIST1_DECLARATION = " getLeafList1();"; + private static final String GET_LEAF1_DECLARATION = " getLeaf1();"; + private static final String IS_LEAF1_BOOLEAN = " Boolean isLeaf1();"; + + private static final char CLOSING_METHOD_BRACE = '}'; + private static final String TAB_CLOSING_METHOD_BRACE = TAB + CLOSING_METHOD_BRACE; + private static final String DTAB_CLOSING_METHOD_BRACE = DOUBLE_TAB + CLOSING_METHOD_BRACE; + + private static final String FOO_GRP_REF = "org.opendaylight.yang.gen.v1.mdsal426.norev.FooGrp"; + private static final String RESOLVED_LEAF_GRP_REF = "org.opendaylight.yang.gen.v1.mdsal426.norev.ResolvedLeafGrp"; + + private static final String UNRESOLVED_GROUPING_REF = + "org.opendaylight.yang.gen.v1.mdsal426.norev.UnresolvedGrouping"; + + private static final String ARG_AS_FOO_GRP = "((" + FOO_GRP_REF + ")arg)"; + + private static final String LEAF2_ASSIGNMENT = "this._leaf2 = arg.getLeaf2();"; + + private static final String TAB_FIELDS_FROM_SIGNATURE = TAB + "public void fieldsFrom(DataObject arg) {"; + private static final String TTAB_SET_IS_VALID_ARG_TRUE = TRIPLE_TAB + "isValidArg = true;"; + private static final String DTAB_INIT_IS_VALID_ARG_FALSE = DOUBLE_TAB + "boolean isValidArg = false;"; + + private File sourcesOutputDir; + private File compiledOutputDir; + private List types; + private Map files; + + @Before + public void before() throws IOException, URISyntaxException { + sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal426"); + compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal426"); + types = generateTestSources("/compilation/mdsal426", sourcesOutputDir); + CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir); + files = getFiles(sourcesOutputDir); + } + + @After + public void after() { + CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir); + } + + @Test + public void testGroupingWithUnresolvedLeafRefs() throws IOException { + verifyReturnType(FOO_GRP, GET_LEAF1_NAME, Types.objectType()); + verifyReturnType(FOO_GRP, GET_LEAFLIST1_NAME, Types.listTypeWildcard()); + + final String content = getFileContent(FOO_GRP); + + assertThat(content, containsString(GET_LEAF1_TYPE_OBJECT)); + assertThat(content, containsString(GET_LEAFLIST1_WILDCARD)); + } + + @Test + public void testLeafLeafrefPointsLeaf() throws IOException { + verifyReturnType(RESOLVED_LEAF_GRP, GET_LEAF1_NAME, Types.STRING); + + final String content = getFileContent(RESOLVED_LEAF_GRP); + + assertThat(content, containsString(GET_LEAF1_TYPE_STRING)); + } + + @Test + public void testLeafLeafrefPointsLeafList() throws IOException { + verifyReturnType(RESOLVED_LEAFLIST_GRP, GET_LEAF1_NAME, Types.STRING); + + final String content = getFileContent(RESOLVED_LEAF_GRP); + + assertThat(content, containsString(GET_LEAF1_TYPE_STRING)); + } + + @Test + public void testLeafListLeafrefPointsLeaf() throws IOException { + verifyReturnType(RESOLVED_LEAF_GRP, GET_LEAFLIST1_NAME, LIST_STRING_TYPE); + + final String content = getFileContent(RESOLVED_LEAF_GRP); + + assertOverriddenGetter(content, GET_LEAFLIST1_STRING); + } + + @Test + public void testLeafListLeafrefPointsLeafList() throws IOException { + verifyReturnType(RESOLVED_LEAFLIST_GRP, GET_LEAFLIST1_NAME, LIST_STRING_TYPE); + + final String content = getFileContent(RESOLVED_LEAFLIST_GRP); + + assertOverriddenGetter(content, GET_LEAFLIST1_STRING); + } + + @Test + public void testGroupingWhichInheritUnresolvedLeafrefAndDoesNotDefineIt() throws IOException { + verifyMethodAbsence(TRANSITIVE_GROUP, GET_LEAF1_NAME); + verifyMethodAbsence(TRANSITIVE_GROUP, GET_LEAFLIST1_NAME); + + final String content = getFileContent(TRANSITIVE_GROUP); + + assertThat(content, not(containsString(GET_LEAF1_DECLARATION))); + assertThat(content, not(containsString(GET_LEAFLIST1_DECLARATION))); + } + + @Test + public void testLeafrefWhichPointsBoolean() throws IOException { + verifyReturnType(UNRESOLVED_GROUPING, GET_LEAF1_NAME, Types.objectType()); + verifyReturnType(BOOLEAN_CONT, GET_LEAF1_NAME, Types.BOOLEAN); + verifyReturnType(BOOLEAN_CONT, IS_LEAF1_NAME, Types.BOOLEAN); + + final String unresolvedGrouping = getFileContent(UNRESOLVED_GROUPING); + final String booleanCont = getFileContent(BOOLEAN_CONT); + + assertNotOverriddenGetter(unresolvedGrouping, GET_LEAF1_TYPE_OBJECT); + assertThat(booleanCont, containsString(GET_LEAF1_DECLARATION)); + } + + @Test + public void testGroupingsUsageWhereLeafrefAlreadyResolved() throws IOException { + leafList1AndLeaf1Absence(BAR_CONT); + leafList1AndLeaf1Absence(BAR_LST); + leafList1AndLeaf1Absence(BAZ_GRP); + } + + private void leafList1AndLeaf1Absence(final String typeName) throws IOException { + verifyMethodAbsence(typeName, GET_LEAF1_NAME); + verifyMethodAbsence(typeName, GET_LEAFLIST1_NAME); + + final String content = getFileContent(typeName); + + assertThat(content, not(containsString(GET_LEAF1_DECLARATION))); + assertThat(content, not(containsString(GET_LEAFLIST1_DECLARATION))); + } + + private static void assertNotOverriddenGetter(final String fileContent, final String getterString) { + assertThat(fileContent, not(containsString("@Override" + System.lineSeparator() + getterString))); + assertThat(fileContent, containsString(getterString)); + } + + private static void assertOverriddenGetter(final String fileContent, final String getterString) { + assertThat(fileContent, containsString("@Override" + System.lineSeparator() + getterString)); + } + + @Test + public void barContBuilderDataObjectTest() throws IOException { + final File file = files.get(getJavaBuilderFileName(BAR_CONT)); + final String content = Files.readString(file.toPath()); + + barContBuilderConstructorResolvedLeafGrpTest(file, content); + barContBuilderConstructorFooGrpTest(file, content); + barContBuilderFieldsFromTest(file, content); + } + + private static void barContBuilderConstructorResolvedLeafGrpTest(final File file, final String content) { + FileSearchUtil.assertFileContainsConsecutiveLines(file, content, + tab("public BarContBuilder(" + RESOLVED_LEAF_GRP_REF + " arg) {"), + doubleTab("this._leaf1 = arg.getLeaf1();"), + doubleTab("this._leafList1 = arg.getLeafList1();"), + doubleTab("this._name = arg.getName();"), + doubleTab(LEAF2_ASSIGNMENT), + TAB_CLOSING_METHOD_BRACE); + } + + private static void barContBuilderFieldsFromTest(final File file, final String content) { + FileSearchUtil.assertFileContainsConsecutiveLines(file, content, + TAB_FIELDS_FROM_SIGNATURE, + DTAB_INIT_IS_VALID_ARG_FALSE, + doubleTab("if (arg instanceof " + FOO_GRP_REF + ") {"), + tripleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.String.class, \"leaf1\", " + + ARG_AS_FOO_GRP + ".getLeaf1());"), + tripleTab("this._leafList1 = CodeHelpers.checkListFieldCast(java.lang.String.class, \"leafList1\", " + + ARG_AS_FOO_GRP + ".getLeafList1());"), + tripleTab("this._leaf2 = " + ARG_AS_FOO_GRP + ".getLeaf2();"), + TTAB_SET_IS_VALID_ARG_TRUE, + DTAB_CLOSING_METHOD_BRACE, + doubleTab("if (arg instanceof " + RESOLVED_LEAF_GRP_REF + ") {"), + tripleTab("this._name = ((" + RESOLVED_LEAF_GRP_REF + ")arg).getName();"), + TTAB_SET_IS_VALID_ARG_TRUE, + DTAB_CLOSING_METHOD_BRACE, + doubleTab("CodeHelpers.validValue(isValidArg, arg, \"[" + FOO_GRP_REF + ", " + RESOLVED_LEAF_GRP_REF + + "]\");"), + TAB_CLOSING_METHOD_BRACE); + } + + private static void barContBuilderConstructorFooGrpTest(final File file, final String content) { + FileSearchUtil.assertFileContainsConsecutiveLines(file, content, + tab("public BarContBuilder(" + FOO_GRP_REF + " arg) {"), + doubleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.String.class, \"leaf1\", " + + "arg.getLeaf1());"), + doubleTab("this._leafList1 = CodeHelpers.checkListFieldCast(java.lang.String.class, \"leafList1\", " + + "arg.getLeafList1());"), + doubleTab(LEAF2_ASSIGNMENT), + TAB_CLOSING_METHOD_BRACE); + } + + @Test + public void booleanContBuilderDataObjectTest() throws IOException { + final File file = files.get(getJavaBuilderFileName(BOOLEAN_CONT)); + final String content = Files.readString(file.toPath()); + + booleanContBuilderFieldsFromTest(file, content); + booleanContBuilderConstructorTest(file, content); + } + + private static void booleanContBuilderFieldsFromTest(final File file, final String content) { + FileSearchUtil.assertFileContainsConsecutiveLines(file, content, + TAB_FIELDS_FROM_SIGNATURE, + DTAB_INIT_IS_VALID_ARG_FALSE, + doubleTab("if (arg instanceof " + UNRESOLVED_GROUPING_REF + ") {"), + tripleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.Boolean.class, \"leaf1\", ((" + + UNRESOLVED_GROUPING_REF + ")arg).getLeaf1());"), + TTAB_SET_IS_VALID_ARG_TRUE, + DTAB_CLOSING_METHOD_BRACE, + doubleTab("CodeHelpers.validValue(isValidArg, arg, \"[" + UNRESOLVED_GROUPING_REF + "]\");"), + TAB_CLOSING_METHOD_BRACE); + } + + private static void booleanContBuilderConstructorTest(final File file, final String content) { + FileSearchUtil.assertFileContainsConsecutiveLines(file, content, + tab("public BooleanContBuilder(" + UNRESOLVED_GROUPING_REF + " arg) {"), + doubleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.Boolean.class, \"leaf1\", " + + "arg.getLeaf1());"), + TAB_CLOSING_METHOD_BRACE); + } + + private static String getJavaFileName(final String name) { + return name + ".java"; + } + + private static String getJavaBuilderFileName(final String name) { + return getJavaFileName(name + "Builder"); + } + + private String getFileContent(final String fileName) throws IOException { + final File file = files.get(getJavaFileName(fileName)); + assertNotNull(Files.lines(file.toPath()).findFirst()); + final String content = Files.readString(Path.of(file.getAbsolutePath())); + assertNotNull(content); + return content; + } + + private void verifyMethodAbsence(final String typeName, final String getterName) { + verifyReturnType(typeName, getterName, null); + } + + private void verifyReturnType(final String typeName, final String getterName, final Type returnType) { + final Type type = typeByName(types, typeName); + assertThat(type, instanceOf(GeneratedType.class)); + final GeneratedType generated = (GeneratedType)type; + assertEquals(returnType, returnTypeByMethodName(generated, getterName)); + } + + private static Type typeByName(final List types, final String name) { + for (final Type type : types) { + if (type.getName().equals(name)) { + return type; + } + } + return null; + } + + private static Type returnTypeByMethodName(final GeneratedType type, final String name) { + for (final MethodSignature m : type.getMethodDefinitions()) { + if (m.getName().equals(name)) { + return m.getReturnType(); + } + } + return null; + } +} diff --git a/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal426/mdsal426.yang b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal426/mdsal426.yang new file mode 100644 index 0000000000..c19a7a6ea1 --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal426/mdsal426.yang @@ -0,0 +1,66 @@ +module mdsal426 { + yang-version 1; + namespace "mdsal426"; + prefix "mdsal426"; + + grouping foo-grp { + leaf leaf1 { + type leafref { + path "../mdsal426:name"; + } + } + leaf-list leaf-list1 { + type leafref { + path "../mdsal426:name"; + } + } + leaf leaf2 { + type string; + } + } + + grouping resolved-leaf-grp { + uses foo-grp; + leaf name { + type string; + } + } + + grouping resolved-leaf-list-grp { + uses foo-grp; + leaf-list name { + type string; + } + } + + container bar-cont { + uses resolved-leaf-grp; + } + + container bar-lst { + uses resolved-leaf-grp; + } + + grouping baz-grp { + uses resolved-leaf-grp; + } + + grouping transitive-group { + uses foo-grp; + } + + grouping unresolved-grouping { + leaf leaf1 { + type leafref { + path "../mdsal426:is-foo"; + } + } + } + + container boolean-cont { + uses unresolved-grouping; + leaf is-foo { + type boolean; + } + } +} \ No newline at end of file diff --git a/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal533/mdsal533.yang b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal533/mdsal533.yang new file mode 100644 index 0000000000..7f72b9edcd --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal533/mdsal533.yang @@ -0,0 +1,36 @@ +module mdsal533 { + yang-version 1; + namespace "mdsal533"; + prefix "mdsal533"; + + grouping foo_grp { + leaf key_leaf1 { + type leafref { + path "../../../mdsal533:foo_list/mdsal533:name"; + } + } + leaf key_leaf2 { + type string; + } + leaf key_leaf3 { + type string; + } + } + + container foo_cont { + list foo_list { + key "name"; + leaf name { + type string { + length "1..255"; + } + } + } + container foo_cont2 { + list foo_list2 { + key "key_leaf1 key_leaf2 key_leaf3"; + uses foo_grp; + } + } + } +} \ No newline at end of file diff --git a/binding/mdsal-binding-test-model/src/main/yang/mdsal426.yang b/binding/mdsal-binding-test-model/src/main/yang/mdsal426.yang new file mode 100644 index 0000000000..c19a7a6ea1 --- /dev/null +++ b/binding/mdsal-binding-test-model/src/main/yang/mdsal426.yang @@ -0,0 +1,66 @@ +module mdsal426 { + yang-version 1; + namespace "mdsal426"; + prefix "mdsal426"; + + grouping foo-grp { + leaf leaf1 { + type leafref { + path "../mdsal426:name"; + } + } + leaf-list leaf-list1 { + type leafref { + path "../mdsal426:name"; + } + } + leaf leaf2 { + type string; + } + } + + grouping resolved-leaf-grp { + uses foo-grp; + leaf name { + type string; + } + } + + grouping resolved-leaf-list-grp { + uses foo-grp; + leaf-list name { + type string; + } + } + + container bar-cont { + uses resolved-leaf-grp; + } + + container bar-lst { + uses resolved-leaf-grp; + } + + grouping baz-grp { + uses resolved-leaf-grp; + } + + grouping transitive-group { + uses foo-grp; + } + + grouping unresolved-grouping { + leaf leaf1 { + type leafref { + path "../mdsal426:is-foo"; + } + } + } + + container boolean-cont { + uses unresolved-grouping; + leaf is-foo { + type boolean; + } + } +} \ No newline at end of file diff --git a/binding/mdsal-binding-test-model/src/main/yang/mdsal533.yang b/binding/mdsal-binding-test-model/src/main/yang/mdsal533.yang new file mode 100644 index 0000000000..7f72b9edcd --- /dev/null +++ b/binding/mdsal-binding-test-model/src/main/yang/mdsal533.yang @@ -0,0 +1,36 @@ +module mdsal533 { + yang-version 1; + namespace "mdsal533"; + prefix "mdsal533"; + + grouping foo_grp { + leaf key_leaf1 { + type leafref { + path "../../../mdsal533:foo_list/mdsal533:name"; + } + } + leaf key_leaf2 { + type string; + } + leaf key_leaf3 { + type string; + } + } + + container foo_cont { + list foo_list { + key "name"; + leaf name { + type string { + length "1..255"; + } + } + } + container foo_cont2 { + list foo_list2 { + key "key_leaf1 key_leaf2 key_leaf3"; + uses foo_grp; + } + } + } +} \ No newline at end of file diff --git a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java index 1952e10cf5..50c77d9134 100644 --- a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java +++ b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java @@ -409,6 +409,48 @@ public final class CodeHelpers { ? requiredClass.cast(obj) : null; } + /** + * Utility method for checking whether a target object is compatible. + * + * @param requiredClass Required class + * @param fieldName name of the field being filled + * @param obj Object to check, may be null + * @return Object cast to required class, if its class matches requirement, or null + * @throws IllegalArgumentException if {@code obj} is not an instance of {@code requiredClass} + * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null + */ + public static @Nullable T checkFieldCast(final @NonNull Class requiredClass, final @NonNull String fieldName, + final @Nullable Object obj) { + try { + return requiredClass.cast(obj); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Invalid input value for property \"" + fieldName + "\"", e); + } + } + + /** + * Utility method for checking whether the items of target list is compatible. + * + * @param requiredClass Required item class + * @param fieldName name of the field being filled + * @param list List, which items should be checked + * @throws IllegalArgumentException if a list item is not instance of {@code requiredItemClass} + * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null + */ + @SuppressWarnings("unchecked") + public static @Nullable List checkListFieldCast(final @NonNull Class requiredClass, + final @NonNull String fieldName, final @Nullable List list) { + if (list != null) { + try { + list.forEach(item -> requiredClass.cast(requireNonNull(item))); + } catch (ClassCastException | NullPointerException e) { + throw new IllegalArgumentException("Invalid input list item for property \"" + requireNonNull(fieldName) + + "\"", e); + } + } + return (List) list; + } + /** * The constant '31' is the result of folding this code: *
diff --git a/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java b/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java
new file mode 100644
index 0000000000..769318ed11
--- /dev/null
+++ b/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.binding;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class CodeHelpersTest {
+    @Test
+    public void testCheckedFieldCast() {
+        assertNull(CodeHelpers.checkFieldCast(CodeHelpersTest.class, "foo", null));
+        assertSame(this, CodeHelpers.checkFieldCast(CodeHelpersTest.class, "foo", this));
+
+        final IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkFieldCast(CodeHelpersTest.class, "foo", new Object()));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+    }
+
+    @Test
+    public void testCheckListFieldCast() {
+        assertNull(CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", null));
+        assertSame(List.of(), CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", List.of()));
+        final var list = List.of(this);
+        assertSame(list, CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", list));
+
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", Collections.singletonList(null)));
+        assertThat(iae.getCause(), instanceOf(NullPointerException.class));
+
+        iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", List.of(new Object())));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+    }
+}