Bug 1869: Fixed binding-data-codec to work with empty type 85/11185/2
authorTony Tkacik <ttkacik@cisco.com>
Mon, 15 Sep 2014 08:39:43 +0000 (10:39 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Mon, 15 Sep 2014 09:18:34 +0000 (11:18 +0200)
Empty type is special case, which does not contain value,
but has only presence characteristic and in Binding Specification v1
it was converted to boolean (true = present, false or null not present)
but new codecs did not have that special handling for empty type
which caused some models to fail.

Fixed places affected by this bug and added test case which now
explicitly tests empty type support.

Change-Id: Idec541dc9da987cebcd46ee65d24c17b7cf9a567
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataNodeContainerSerializerSource.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingCodecContext.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ValueTypeCodec.java
code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/EmptyLeafTest.java [new file with mode: 0644]
code-generator/binding-test-model/src/main/yang/opendaylight-yangtools-augment-test.yang
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/AbstractImmutableNormalizedValueNode.java

index 07d6602c2dd7d0025f0586c2a278718236bdd755..9b8faba4cb67a2a3bf36215dbc9796937ed9b48d 100644 (file)
@@ -7,10 +7,8 @@
  */package org.opendaylight.yangtools.binding.data.codec.gen.impl;
 
 import com.google.common.base.Preconditions;
-
 import java.util.HashMap;
 import java.util.Map;
-
 import org.opendaylight.yangtools.binding.data.codec.util.ChoiceDispatchSerializer;
 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType;
 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature;
@@ -31,6 +29,7 @@ import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
 
 abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSource {
 
@@ -106,7 +105,7 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
             while (rootType.getBaseType() != null) {
                 rootType = rootType.getBaseType();
             }
-            if(rootType instanceof BooleanTypeDefinition) {
+            if(rootType instanceof BooleanTypeDefinition || rootType instanceof EmptyTypeDefinition) {
                 prefix = "is";
             }
         }
index dfef41afc0aedd37bac71784384be0b3e816db95..881a62bc2e0c0abe4da9baf94c9a5536a9bf0076 100644 (file)
@@ -9,7 +9,6 @@ package org.opendaylight.yangtools.binding.data.codec.impl;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
-
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -25,10 +24,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.Callable;
-
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-
 import org.opendaylight.yangtools.binding.data.codec.impl.NodeCodecContext.CodecContextFactory;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.concepts.Immutable;
@@ -53,6 +50,7 @@ import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
@@ -207,7 +205,7 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
         while (typeDef.getBaseType() != null) {
             typeDef = typeDef.getBaseType();
         }
-        if (typeDef instanceof BooleanTypeDefinition) {
+        if (typeDef instanceof BooleanTypeDefinition || typeDef instanceof EmptyTypeDefinition) {
             return "is" + suffix;
         }
         return GETTER_PREFIX + suffix;
@@ -246,6 +244,14 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
 
 
     private Codec<Object, Object> getCodec(final Class<?> valueType, final DataSchemaNode schema) {
+        final TypeDefinition<?> instantiatedType;
+        if (schema instanceof LeafSchemaNode) {
+            instantiatedType = ((LeafSchemaNode) schema).getType();
+        } else if (schema instanceof LeafListSchemaNode) {
+            instantiatedType = ((LeafListSchemaNode) schema).getType();
+        } else {
+            throw new IllegalArgumentException("Unsupported leaf node type " + schema.getClass());
+        }
         if (Class.class.equals(valueType)) {
             @SuppressWarnings({ "unchecked", "rawtypes" })
             final Codec<Object, Object> casted = (Codec) identityCodec;
@@ -254,18 +260,12 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
             @SuppressWarnings({ "unchecked", "rawtypes" })
             final Codec<Object, Object> casted = (Codec) instanceIdentifierCodec;
             return casted;
-        } else if (BindingReflections.isBindingClass(valueType)) {
-            final TypeDefinition<?> instantiatedType;
-            if (schema instanceof LeafSchemaNode) {
-                instantiatedType = ((LeafSchemaNode) schema).getType();
-            } else if (schema instanceof LeafListSchemaNode) {
-                instantiatedType = ((LeafListSchemaNode) schema).getType();
-            } else {
-                instantiatedType = null;
-            }
-            if (instantiatedType != null) {
-                return getCodec(valueType, instantiatedType);
+        } else if (Boolean.class.equals(valueType)) {
+            if(instantiatedType instanceof EmptyTypeDefinition) {
+                return ValueTypeCodec.EMPTY_CODEC;
             }
+        } else if (BindingReflections.isBindingClass(valueType)) {
+                            return getCodec(valueType, instantiatedType);
         }
         return ValueTypeCodec.NOOP_CODEC;
     }
index e432c55b005d4d81f6b80740ddb1bf2743574140..d8536ee667e9aae07184f25f0e74bf943e72dd31 100644 (file)
@@ -15,6 +15,7 @@ import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
 
 /**
@@ -59,11 +60,46 @@ abstract class ValueTypeCodec implements Codec<Object, Object> {
         }
     };
 
+    public static final SchemaUnawareCodec EMPTY_CODEC = new SchemaUnawareCodec() {
+
+        @Override
+        public Object serialize(Object arg0) {
+            // Empty type has null value in NormalizedNode and Composite Node
+            // representation
+            return null;
+        }
+
+        @Override
+        public Object deserialize(Object arg0) {
+            /* Empty type has boolean.TRUE representation in Binding-aware world
+            *  otherwise it is null / false.
+            *  So when codec is triggered, empty leaf is present, that means we
+            *  are safe to return true.
+            */
+            return Boolean.TRUE;
+        }
+    };
+
+    private static final Callable<? extends SchemaUnawareCodec> EMPTY_LOADER = new Callable<SchemaUnawareCodec>() {
+
+        @Override
+        public SchemaUnawareCodec call() throws Exception {
+            return EMPTY_CODEC;
+        }
+    };
+
 
     public static SchemaUnawareCodec getCodecFor(final Class<?> typeClz, final TypeDefinition<?> def) {
         if (BindingReflections.isBindingClass(typeClz)) {
             return getCachedSchemaUnawareCodec(typeClz, getCodecLoader(typeClz, def));
         }
+        TypeDefinition<?> rootType = def;
+        while (rootType.getBaseType() != null) {
+            rootType = rootType.getBaseType();
+        }
+        if(rootType instanceof EmptyTypeDefinition) {
+            return EMPTY_CODEC;
+        }
         return NOOP_CODEC;
     }
 
@@ -85,6 +121,8 @@ abstract class ValueTypeCodec implements Codec<Object, Object> {
             return EnumerationCodec.loader(typeClz, (EnumTypeDefinition) rootType);
         } else if (rootType instanceof BitsTypeDefinition) {
             return BitsCodec.loader(typeClz, (BitsTypeDefinition) rootType);
+        } else if (rootType instanceof EmptyTypeDefinition) {
+            return EMPTY_LOADER;
         }
         return EncapsulatedValueCodec.loader(typeClz);
     }
diff --git a/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/EmptyLeafTest.java b/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/EmptyLeafTest.java
new file mode 100644 (file)
index 0000000..53258f8
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.binding.data.codec.test;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map.Entry;
+import javassist.ClassPool;
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.RpcComplexUsesAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.RpcComplexUsesAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.TreeComplexUsesAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.TreeLeafOnlyAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.complex.from.grouping.ListViaUses;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.top.top.level.list.choice.in.list.EmptyLeaf;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.augment.rev140709.top.top.level.list.choice.in.list.EmptyLeafBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.Top;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelListBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelListKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.top.level.list.ChoiceInList;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+
+public class EmptyLeafTest extends AbstractBindingRuntimeTest {
+
+    private static final TopLevelListKey TOP_FOO_KEY = new TopLevelListKey("foo");
+    private static final InstanceIdentifier<TopLevelList> BA_TOP_LEVEL_LIST = InstanceIdentifier.builder(Top.class)
+            .child(TopLevelList.class, TOP_FOO_KEY).toInstance();
+    private static final InstanceIdentifier<TreeLeafOnlyAugment> BA_TREE_LEAF_ONLY = BA_TOP_LEVEL_LIST
+            .augmentation(TreeLeafOnlyAugment.class);
+    private static final InstanceIdentifier<TreeComplexUsesAugment> BA_TREE_COMPLEX_USES = BA_TOP_LEVEL_LIST
+            .augmentation(TreeComplexUsesAugment.class);
+    private static final QName SIMPLE_VALUE_QNAME = QName.create(TreeComplexUsesAugment.QNAME, "simple-value");
+
+    private BindingNormalizedNodeCodecRegistry registry;
+
+    @Override
+    public void setup() {
+        super.setup();
+        JavassistUtils utils = JavassistUtils.forClassPool(ClassPool.getDefault());
+        registry = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(utils));
+        registry.onBindingRuntimeContextUpdated(getRuntimeContext());
+    }
+
+    @Test
+    public void testCaseWithEmptyLeafType() {
+        TopLevelList withEmptyCase = new TopLevelListBuilder()
+            .setKey(TOP_FOO_KEY)
+            .setChoiceInList(new EmptyLeafBuilder().setEmptyType(true).build())
+            .build();
+        Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> dom = registry.toNormalizedNode(BA_TOP_LEVEL_LIST, withEmptyCase);
+        Entry<InstanceIdentifier<?>, DataObject> readed = registry.fromNormalizedNode(dom.getKey(),dom.getValue());
+        ChoiceInList list = ((TopLevelList) readed.getValue()).getChoiceInList();
+        assertTrue(list instanceof EmptyLeaf);
+        assertTrue(((EmptyLeaf) list).isEmptyType());
+    }
+
+    private RpcComplexUsesAugment createComplexData() {
+        return new RpcComplexUsesAugmentBuilder()
+        .setContainerWithUses(new ContainerWithUsesBuilder()
+            .setLeafFromGrouping("foo")
+        .build())
+        .setListViaUses(Collections.<ListViaUses>emptyList())
+        .build();
+    }
+
+}
index 6d155becc7628237edfc133df77c594f5fcf58ef..7363f38c46ff87e5223cc2f91011d686949c667f 100644 (file)
@@ -97,6 +97,11 @@ module opendaylight-yangtools-augment-test {
         case complex-via-uses {
             uses complex-from-grouping;
         }
+        case empty-leaf {
+            leaf empty-type {
+                type empty;
+            }
+        }
     }
 
     augment "/test:put-top/test:input/test:top-level-list/test:choice-in-list" {
index beb797975cf696cf8189f69532c1d4a36909376b..bd8252159ae58b9c2e898f6d0de573d34fbd40e6 100644 (file)
@@ -20,7 +20,11 @@ public abstract class AbstractImmutableNormalizedValueNode<K extends YangInstanc
     protected AbstractImmutableNormalizedValueNode(final K nodeIdentifier, final V value) {
         super(nodeIdentifier);
         if (value == null) {
-            LOGGER.warn("The value of node " + nodeIdentifier.getNodeType() + " is null");
+            /*
+             * Null value is allowed for empty type definition so it should be debug,
+             * but still we are logging it in case we need to debug missing values.
+             */
+            LOGGER.debug("The value of node {} is null",nodeIdentifier.getNodeType());
         }
         this.value = value;
     }