Specialize relative leafref types during instantiation 01/88401/86
authormiroslav.kovac <miroslav.kovac@pantheon.tech>
Wed, 11 Mar 2020 15:42:29 +0000 (16:42 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 18 Dec 2020 22:44:52 +0000 (23:44 +0100)
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 <miroslav.kovac@pantheon.tech>
Signed-off-by: Ilya Igushev <illia.ihushev@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
21 files changed:
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java
binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/impl/SpecializingLeafrefTest.java [new file with mode: 0644]
binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java
binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/Types.java
binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/generated/type/builder/AbstractGeneratedType.java
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractBuilderTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderImplTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/JavaFileTemplate.java
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/CompilationTest.java
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/FileSearchUtil.java
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/SpecializingLeafrefTest.java [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal426/mdsal426.yang [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal533/mdsal533.yang [new file with mode: 0644]
binding/mdsal-binding-test-model/src/main/yang/mdsal426.yang [new file with mode: 0644]
binding/mdsal-binding-test-model/src/main/yang/mdsal533.yang [new file with mode: 0644]
binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java
binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java [new file with mode: 0644]

index 99dee6fda3a74d0957b8671ce7c34efce768a891..e8484a4bb10e90ac9295c616cfd727ff21726bde 100644 (file)
@@ -359,7 +359,8 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
             final Class<?> parentClass, final Map<String, DataSchemaNode> getterToLeafSchema) {
         final Map<Method, ValueNodeCodecContext> 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 (file)
index 0000000..33ba390
--- /dev/null
@@ -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<BooleanCont> BOOLEAN_CONT_II = InstanceIdentifier
+            .builder(BooleanCont.class).build();
+
+    private static final InstanceIdentifier<BarCont> BAR_CONT_II = InstanceIdentifier
+            .builder(BarCont.class).build();
+
+    @Test
+    public void specifiedBooleanLeafTest() {
+        final BooleanCont booleanCont  = new BooleanContBuilder().setIsFoo(true).build();
+
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> 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<YangInstanceIdentifier, NormalizedNode<?, ?>> 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<String> testList = ImmutableList.of("test");
+        final BarCont barCont  = new BarContBuilder().setLeafList1(testList).build();
+
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> res = codecContext
+                .toNormalizedNode(BAR_CONT_II, barCont);
+
+        final BarCont barContAfterConverting = (BarCont)codecContext
+                .fromNormalizedNode(res.getKey(), res.getValue()).getValue();
+
+        assertEquals(barContAfterConverting.getLeafList1(), testList);
+    }
+}
index 15e3f4b7442d1e4be1cd57d4f02d1ed89987f616..d3f95ebd1308281325b3fa5c81ee318e9cc41c3e 100644 (file)
@@ -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<AnnotationType> 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.
+     *
+     * <p>
+     * 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<? extends TypeDefinition<?>> type) {
+        return type instanceof LeafrefTypeDefinition && !((LeafrefTypeDefinition) type).getPathStatement().isAbsolute();
+    }
+
     /**
      * Adds the methods to <code>typeBuilder</code> what represents subnodes of node for which <code>typeBuilder</code>
      * was created.
@@ -1150,35 +1274,50 @@ abstract class AbstractTypeGenerator {
     }
 
     /**
-     * Adds to <code>typeBuilder</code> a method which is derived from <code>schemaNode</code>.
+     * Adds to {@code typeBuilder} a method which is derived from {@code schemaNode}.
      *
      * @param node data schema node which is added to <code>typeBuilder</code> as a method
      * @param typeBuilder generated type builder to which is <code>schemaNode</code> 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 <code>choiceNode</code> 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 (<code>basePackageName</code>)
@@ -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 <code>leaf</code> to the getter method which is added to <code>typeBuilder</code>.
+     * 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 <code>leaf</code> mapping
-     * @param leaf leaf schema node which is mapped as getter method which is added to <code>typeBuilder</code>
-     * @param module Module in which type was defined
-     * @return boolean value
-     *         <ul>
-     *         <li>false - if <code>leaf</code> or <code>typeBuilder</code> are
-     *         null</li>
-     *         <li>true - in other cases</li>
-     *         </ul>
+     * @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 <code>node</code> leaf list schema node to getter method of <code>typeBuilder</code>.
      *
+     * @param context module
      * @param typeBuilder generated type builder to which is <code>node</code> added as getter method
      * @param node leaf list schema node which is added to <code>typeBuilder</code> as getter method
-     * @param module module
-     * @return boolean value
-     *         <ul>
-     *         <li>true - if <code>node</code>, <code>typeBuilder</code>,
-     *         nodeName equal null or <code>node</code> is added by <i>uses</i></li>
-     *         <li>false - other cases</li>
-     *         </ul>
      */
-    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<? extends 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) {
index deae83c0e82b30f16e8ef13d1834d683713e8966..97835bd02a04dd8c5718ead0e79716067604e36c 100644 (file)
@@ -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}&lt;?&gt;.
+     *
+     * @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);
     }
index 927751f11078cdf258394eacb7b286c1553ef643..19771709e0d01c9e904187834e4196dd96cef6b2 100644 (file)
@@ -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 <T> Set<T> makeUnmodifiable(final Set<T> 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<GeneratedType> toUnmodifiableEnclosedTypes(
             final List<GeneratedTypeBuilder> enclosedGenTypeBuilders,
             final List<GeneratedTOBuilder> enclosedGenTOBuilders) {
@@ -127,6 +140,14 @@ abstract class AbstractGeneratedType extends AbstractBaseType implements Generat
         return makeUnmodifiable(methods);
     }
 
+    protected final Set<MethodSignature> toUnmodifiableMethods(final Set<MethodSignatureBuilder> getters) {
+        final Set<MethodSignature> methods = new HashSet<>(getters.size());
+        for (final MethodSignatureBuilder methodBuilder : getters) {
+            methods.add(methodBuilder.toInstance(this));
+        }
+        return makeUnmodifiable(methods);
+    }
+
     protected final List<Enumeration> toUnmodifiableEnumerations(final List<EnumBuilder> enumBuilders) {
         final List<Enumeration> enums = new ArrayList<>(enumBuilders.size());
         for (final EnumBuilder enumBuilder : enumBuilders) {
index 85147c869b8871e5162f2cbc710566d4bf32dcf0..5dcce2a4c9b28f7b979bb4f80360c65d41222aa9 100644 (file)
@@ -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])
+    }
 }
index f805ef953b211669cb6e76cbd133f99c204f6b9f..224d212df5da03a771730e02dacb19d892f91ff3 100644 (file)
@@ -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 <code>field</code>
      *
index 040e392cc5a8019d935869301d2f30aa9bff6459..25c07c25c666e102e1b3d418d3a50001970e8af1 100644 (file)
@@ -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<MethodSignature> 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 <code>hashCode()</code>.
      *
index bd844616df4382e4620de3219595c8c253492d23..8bfa8817a3b1dd2a43cea6c61fa33aeddeb4d0d9 100644 (file)
@@ -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 <code>genType</code> equals <code>null</code>
      */
     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<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.
      */
@@ -177,7 +210,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
          * </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
         */
     '''
 
@@ -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<Type> getBaseIfcs(GeneratedType type) {
         val List<Type> 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])
-    }
 }
index 6687c9efb179e32eeb3e19968467c025bc5e9564..b778c9e45cad56086020232ded23185ce1ba44e2 100644 (file)
@@ -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»();
         '''
     }
index bef1e9edd9efdc7be5d8bb36d430d44ce1d09f2d..6997b4d3f73ccd749556990f6d9614e2b8f8c4d8 100644 (file)
@@ -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<MethodSignature> 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<MethodSignature> getterByName(final Iterable<MethodSignature> 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 <code>methods</code>.
      *
index e64c018e99a781e7737119fa374016d2b4ad6983..3e9b7b6cdbdb0cb6ab745ba0d716635b3b542d01 100644 (file)
@@ -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();
index 83b14b0470930850d349a672a0afb7288d28b71b..44d0b41ed9b062c1160129e007e75088d1b9e271 100644 (file)
@@ -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);
index 599c712fe54357932bfefcf10df76803a6fe178a..b57dfd54722d0dab49a4ce22b960c026a3bef7cd 100644 (file)
@@ -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<String, File> 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 (file)
index 0000000..b98b0b3
--- /dev/null
@@ -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<String> 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<Type> types;
+    private Map<String, File> 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<Type> 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 (file)
index 0000000..c19a7a6
--- /dev/null
@@ -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 (file)
index 0000000..7f72b9e
--- /dev/null
@@ -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 (file)
index 0000000..c19a7a6
--- /dev/null
@@ -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 (file)
index 0000000..7f72b9e
--- /dev/null
@@ -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
index 1952e10cf536202d01721bf47df4aec894856825..50c77d9134dcc287b021488cb1c7c8ccdbdf6f42 100644 (file)
@@ -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 <T> @Nullable T checkFieldCast(final @NonNull Class<T> 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 <T> @Nullable List<T> 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<T>) list;
+    }
+
     /**
      * The constant '31' is the result of folding this code:
      * <pre>
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 (file)
index 0000000..769318e
--- /dev/null
@@ -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));
+    }
+}