Bug 3067: Improved error reporting in Binding Data Codec 01/19301/3
authorTony Tkacik <ttkacik@cisco.com>
Wed, 29 Apr 2015 13:35:08 +0000 (15:35 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Thu, 30 Apr 2015 13:02:00 +0000 (15:02 +0200)
Error reporting in Binding Data Codec was reworked
to throw three additional subclasses of IllegalArgumentException
for specific case of failure:

MissingSchema and MissingSchemaForClass - exception thrown
when schema context associated with codec does not contain
models for supplied class or DOM argument.

IncorrectNesting is thrown when schema is available an user
constructed data with invalid nesting (bypassed generic
compile-time checks).

The checks to determine type of exception are done only
if error condition is detected, so non-error fast path
should not be affected by advanced checks.

Added test which tests these types of exceptions.

Change-Id: Iad020a42317ab46df4d2240568fd6e8205383857
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ChoiceNodeCodecContext.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataContainerCodecContext.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataObjectCodecContext.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/IncorrectNestingException.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/LeafNodeCodecContext.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaException.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaForClassException.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/SchemaRootCodecContext.java
code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/ExceptionReportingTest.java [new file with mode: 0644]
code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/BindingRuntimeContext.java

index 5240ecf6104ea600ad9cd092871b957362386657..7ab7e6b346e0dd972b9727ddbfb4f02f9e716afa 100644 (file)
@@ -23,6 +23,7 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
@@ -39,27 +40,27 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
 
     public ChoiceNodeCodecContext(final DataContainerCodecPrototype<ChoiceSchemaNode> prototype) {
         super(prototype);
-        Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChildBuilder = new HashMap<>();
-        Map<Class<?>, DataContainerCodecPrototype<?>> byClassBuilder = new HashMap<>();
-        Map<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClassBuilder = new HashMap<>();
-        Set<Class<?>> potentialSubstitutions = new HashSet<>();
+        final Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChildBuilder = new HashMap<>();
+        final Map<Class<?>, DataContainerCodecPrototype<?>> byClassBuilder = new HashMap<>();
+        final Map<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClassBuilder = new HashMap<>();
+        final Set<Class<?>> potentialSubstitutions = new HashSet<>();
         // Walks all cases for supplied choice in current runtime context
-        for (Class<?> caze : factory().getRuntimeContext().getCases(getBindingClass())) {
+        for (final Class<?> caze : factory().getRuntimeContext().getCases(getBindingClass())) {
             // We try to load case using exact match thus name
             // and original schema must equals
-            DataContainerCodecPrototype<ChoiceCaseNode> cazeDef = loadCase(caze);
+            final DataContainerCodecPrototype<ChoiceCaseNode> cazeDef = loadCase(caze);
             // If we have case definition, this case is instantiated
             // at current location and thus is valid in context of parent choice
             if (cazeDef != null) {
                 byClassBuilder.put(cazeDef.getBindingClass(), cazeDef);
                 // Updates collection of case children
                 @SuppressWarnings("unchecked")
-                Class<? extends DataObject> cazeCls = (Class<? extends DataObject>) caze;
-                for (Class<? extends DataObject> cazeChild : BindingReflections.getChildrenClasses(cazeCls)) {
+                final Class<? extends DataObject> cazeCls = (Class<? extends DataObject>) caze;
+                for (final Class<? extends DataObject> cazeChild : BindingReflections.getChildrenClasses(cazeCls)) {
                     byCaseChildClassBuilder.put(cazeChild, cazeDef);
                 }
                 // Updates collection of YANG instance identifier to case
-                for (DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
+                for (final DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
                     byYangCaseChildBuilder.put(new NodeIdentifier(cazeChild.getQName()), cazeDef);
                 }
             } else {
@@ -71,7 +72,7 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
             }
         }
 
-        Map<Class<?>, DataContainerCodecPrototype<?>> bySubstitutionBuilder = new HashMap<>();
+        final Map<Class<?>, DataContainerCodecPrototype<?>> bySubstitutionBuilder = new HashMap<>();
         /*
          * Walks all cases which are not directly instantiated and
          * tries to match them to instantiated cases - represent same data as instantiated case,
@@ -79,8 +80,8 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
          * binding specification, that if choice is in grouping schema path location is lost,
          * and users may use incorrect case class using copy builders.
          */
-        for(Class<?> substitution : potentialSubstitutions) {
-            search: for(Entry<Class<?>, DataContainerCodecPrototype<?>> real : byClassBuilder.entrySet()) {
+        for(final Class<?> substitution : potentialSubstitutions) {
+            search: for(final Entry<Class<?>, DataContainerCodecPrototype<?>> real : byClassBuilder.entrySet()) {
                 if(BindingReflections.isSubstitutionFor(substitution, real.getKey())) {
                     bySubstitutionBuilder.put(substitution, real.getValue());
                     break search;
@@ -96,18 +97,17 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
 
     @SuppressWarnings("unchecked")
     @Override
-    public <DV extends DataObject> DataContainerCodecContext<DV, ?> streamChild(Class<DV> childClass) {
-        DataContainerCodecPrototype<?> child = byClass.get(childClass);
-        Preconditions.checkArgument(child != null,"Supplied class is not valid case",childClass);
-        return (DataContainerCodecContext<DV, ?>) child.get();
+    public <DV extends DataObject> DataContainerCodecContext<DV, ?> streamChild(final Class<DV> childClass) {
+        final DataContainerCodecPrototype<?> child = byClass.get(childClass);
+        return (DataContainerCodecContext<DV, ?>) childNonNull(child,childClass,"Supplied class %s is not valid case").get();
     }
 
 
     @SuppressWarnings("unchecked")
     @Override
     public <DV extends DataObject> Optional<DataContainerCodecContext<DV, ?>> possibleStreamChild(
-            Class<DV> childClass) {
-        DataContainerCodecPrototype<?> child = byClass.get(childClass);
+            final Class<DV> childClass) {
+        final DataContainerCodecPrototype<?> child = byClass.get(childClass);
         if(child != null) {
             return Optional.<DataContainerCodecContext<DV,?>>of((DataContainerCodecContext<DV, ?>) child.get());
         }
@@ -119,7 +119,7 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
     }
 
     protected DataContainerCodecPrototype<ChoiceCaseNode> loadCase(final Class<?> childClass) {
-        Optional<ChoiceCaseNode> childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(schema(), childClass);
+        final Optional<ChoiceCaseNode> childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(schema(), childClass);
         if (childSchema.isPresent()) {
             return DataContainerCodecPrototype.from(childClass, childSchema.get(), factory());
         }
@@ -130,48 +130,47 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
 
     @Override
     public NodeCodecContext<?> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) {
-        DataContainerCodecPrototype<?> cazeProto = byYangCaseChild.get(arg);
-        Preconditions.checkArgument(cazeProto != null, "Argument %s is not valid child of %s", arg, schema());
+        final DataContainerCodecPrototype<?> cazeProto = byYangCaseChild.get(arg);
+        childNonNull(cazeProto != null, arg,"Argument %s is not valid child of %s", arg, schema());
         return cazeProto.get().yangPathArgumentChild(arg);
     }
 
     @SuppressWarnings("unchecked")
     @Override
+    @Nullable
     public D deserialize(final NormalizedNode<?, ?> data) {
-        Preconditions
-                .checkArgument(data instanceof org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode);
-        NormalizedNodeContainer<?, ?, NormalizedNode<?,?>> casted = (NormalizedNodeContainer<?, ?, NormalizedNode<?,?>>) data;
-        NormalizedNode<?, ?> first = Iterables.getFirst(casted.getValue(), null);
+        Preconditions.checkArgument(data instanceof ChoiceNode);
+        final NormalizedNodeContainer<?, ?, NormalizedNode<?,?>> casted = (NormalizedNodeContainer<?, ?, NormalizedNode<?,?>>) data;
+        final NormalizedNode<?, ?> first = Iterables.getFirst(casted.getValue(), null);
 
         if (first == null) {
             return null;
         }
-        DataContainerCodecPrototype<?> caze = byYangCaseChild.get(first.getIdentifier());
+        final DataContainerCodecPrototype<?> caze = byYangCaseChild.get(first.getIdentifier());
         return (D) caze.get().deserialize(data);
     }
 
-    @Nullable DataContainerCodecContext<?,?> getCazeByChildClass(final @Nonnull Class<? extends DataObject> type) {
-        final DataContainerCodecPrototype<?> protoCtx = byCaseChildClass.get(type);
-        if(protoCtx != null) {
-            return protoCtx.get();
-        }
-        return null;
+    DataContainerCodecContext<?, ?> getCazeByChildClass(final @Nonnull Class<? extends DataObject> type) {
+        final DataContainerCodecPrototype<?> protoCtx =
+                childNonNull(byCaseChildClass.get(type), type, "Class %s is not child of any cases for %s", type,
+                        bindingArg());
+        return protoCtx.get();
     }
 
     @Override
-    protected Object deserializeObject(NormalizedNode<?, ?> normalizedNode) {
+    protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
         return deserialize(normalizedNode);
     }
 
     @Override
-    public PathArgument deserializePathArgument(YangInstanceIdentifier.PathArgument arg) {
+    public PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
         Preconditions.checkArgument(getDomPathArgument().equals(arg));
         return null;
     }
 
     @Override
     public YangInstanceIdentifier.PathArgument serializePathArgument(
-            PathArgument arg) {
+            final PathArgument arg) {
         // FIXME: check for null, since binding container is null.
         return getDomPathArgument();
     }
index 371a181ce4111bc96f195d32fceb0f3a092b9a76..ef21235d59df191eefc00bb9511bc7be465ea4d6 100644 (file)
@@ -12,6 +12,7 @@ import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.List;
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeCachingCodec;
 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
@@ -19,6 +20,7 @@ import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.DataObjectSerializer;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
@@ -146,6 +148,36 @@ abstract class DataContainerCodecContext<D extends DataObject,T> extends NodeCod
         return  new BindingToNormalizedStreamWriter(this, domWriter);
     }
 
+    @Nonnull
+    protected final <V> V childNonNull(@Nullable final V nullable, final YangInstanceIdentifier.PathArgument child,
+            final String message, final Object... args) {
+        if (nullable != null) {
+            return nullable;
+        }
+        MissingSchemaException.checkModulePresent(factory().getRuntimeContext().getSchemaContext(), child);
+        throw IncorrectNestingException.create(message, args);
+    }
+
+    @Nonnull
+    protected final <V> V childNonNull(@Nullable final V nullable, final QName child, final String message,
+            final Object... args) {
+        if (nullable != null) {
+            return nullable;
+        }
+        MissingSchemaException.checkModulePresent(factory().getRuntimeContext().getSchemaContext(), child);
+        throw IncorrectNestingException.create(message, args);
+    }
+
+    @Nonnull
+    protected final <V> V childNonNull(@Nullable final V nullable, final Class<?> childClass, final String message,
+            final Object... args) {
+        if (nullable != null) {
+            return nullable;
+        }
+        MissingSchemaForClassException.check(factory().getRuntimeContext(), childClass);
+        throw IncorrectNestingException.create(message, args);
+    }
+
     DataObjectSerializer eventStreamSerializer() {
         if(eventStreamSerializer == null) {
             eventStreamSerializer = factory().getEventStreamSerializer(getBindingClass());
index 8302aed0e6348d283b45d974bf8c2c105ba77edb..0ca1d2b8504d3183bb01783312eb1ea4717dcc23 100644 (file)
@@ -7,8 +7,6 @@
  */
 package org.opendaylight.yangtools.binding.data.codec.impl;
 
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
-
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
@@ -33,6 +31,7 @@ import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy
 import org.opendaylight.yangtools.sal.binding.model.api.Type;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
@@ -155,8 +154,7 @@ abstract class DataObjectCodecContext<D extends DataObject,T extends DataNodeCon
                 }
             }
         }
-        Preconditions.checkArgument(childProto != null, " Child %s is not valid child.",childClass);
-        return (DataContainerCodecContext<DV, ?>) childProto.get();
+        return (DataContainerCodecContext<DV, ?>) childNonNull(childProto, childClass, " Child %s is not valid child.").get();
     }
 
 
@@ -177,23 +175,17 @@ abstract class DataObjectCodecContext<D extends DataObject,T extends DataNodeCon
 
         final Class<? extends DataObject> argType = arg.getType();
         final DataContainerCodecPrototype<?> ctxProto = byBindingArgClass.get(argType);
-        if(ctxProto != null) {
-            final DataContainerCodecContext<?,?> context = ctxProto.get();
-            if(context instanceof ChoiceNodeCodecContext) {
-                final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) context;
-                final DataContainerCodecContext<?,?> caze = choice.getCazeByChildClass(arg.getType());
-                if(caze != null) {
-                    choice.addYangPathArgument(arg, builder);
-                    caze.addYangPathArgument(arg, builder);
-                    return caze.bindingPathArgumentChild(arg, builder);
-                }
-                return null;
-            }
-            context.addYangPathArgument(arg, builder);
-            return context;
+        final DataContainerCodecContext<?, ?> context =
+                childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get();
+        if (context instanceof ChoiceNodeCodecContext) {
+            final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) context;
+            final DataContainerCodecContext<?, ?> caze = choice.getCazeByChildClass(arg.getType());
+            choice.addYangPathArgument(arg, builder);
+            caze.addYangPathArgument(arg, builder);
+            return caze.bindingPathArgumentChild(arg, builder);
         }
-        // Argument is not valid child.
-        return null;
+        context.addYangPathArgument(arg, builder);
+        return context;
     }
 
     @SuppressWarnings("unchecked")
@@ -203,14 +195,13 @@ abstract class DataObjectCodecContext<D extends DataObject,T extends DataNodeCon
             arg = new NodeIdentifier(arg.getNodeType());
         }
         final NodeContextSupplier childSupplier = byYang.get(arg);
-        Preconditions.checkArgument(childSupplier != null, "Argument %s is not valid child of %s", arg, schema());
+        childNonNull(childSupplier != null, arg, "Argument %s is not valid child of %s", arg, schema());
         return (NodeCodecContext<D>) childSupplier.get();
     }
 
     protected final LeafNodeCodecContext<?> getLeafChild(final String name) {
         final LeafNodeCodecContext<?> value = leafChild.get(name);
-        Preconditions.checkArgument(value != null, "Leaf %s is not valid for %s", name, getBindingClass());
-        return value;
+        return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass());
     }
 
     private DataContainerCodecPrototype<?> loadChildPrototype(final Class<?> childClass) {
@@ -248,8 +239,9 @@ abstract class DataObjectCodecContext<D extends DataObject,T extends DataNodeCon
                 childSchema = null;
             }
         }
-        Preconditions.checkArgument(childSchema != null, "Node %s does not have child named %s", schema(), childClass);
-        return DataContainerCodecPrototype.from(childClass, childSchema, factory());
+        final DataSchemaNode nonNullChild =
+                childNonNull(childSchema, childClass, "Node %s does not have child named %s", schema(), childClass);
+        return DataContainerCodecPrototype.from(childClass, nonNullChild, factory());
     }
 
     private DataContainerCodecPrototype<?> getAugmentationPrototype(final Type value) {
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/IncorrectNestingException.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/IncorrectNestingException.java
new file mode 100644 (file)
index 0000000..5e7ec6a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ *
+ * Thrown where incorrect nesting of data structures was detected
+ * and was caused by user.
+ *
+ */
+public class IncorrectNestingException extends IllegalArgumentException {
+
+    private static final long serialVersionUID = 1L;
+
+    protected IncorrectNestingException(final String message) {
+        super(message);
+    }
+
+    public static IncorrectNestingException create(final String message, final Object... args) {
+        return new IncorrectNestingException(String.format(message, args));
+    }
+
+    public static void check(final boolean check, final String message, final Object... args) {
+        if(!check) {
+            throw IncorrectNestingException.create(message, args);
+        }
+    }
+
+    @Nonnull
+    public static <V> V checkNonNull(@Nullable final V nullable, final String message, final Object... args) {
+        if(nullable != null) {
+            return nullable;
+        }
+        throw IncorrectNestingException.create(message, args);
+    }
+}
index e8d115c16da8a71fa8fe51bde68d4fa16edbbcb8..81d9f3a609e75af35da4f76af9aabc51a4db1b0e 100644 (file)
@@ -64,58 +64,57 @@ final class LeafNodeCodecContext<D extends DataObject> extends NodeCodecContext<
     }
 
     @Override
-    public BindingCodecTreeNode<?> bindingPathArgumentChild(PathArgument arg,
-            List<YangInstanceIdentifier.PathArgument> builder) {
-        return null;
+    public BindingCodecTreeNode<?> bindingPathArgumentChild(final PathArgument arg,
+            final List<YangInstanceIdentifier.PathArgument> builder) {
+        throw new IllegalArgumentException("Leaf does not have children");
     }
 
     @Override
     public BindingNormalizedNodeCachingCodec<D> createCachingCodec(
-            ImmutableCollection<Class<? extends DataObject>> cacheSpecifier) {
-        return null;
+            final ImmutableCollection<Class<? extends DataObject>> cacheSpecifier) {
+        throw new UnsupportedOperationException("Leaves does not support caching codec.");
     }
 
     @Override
     public Class<D> getBindingClass() {
-        return null;
+        throw new UnsupportedOperationException("Leaf does not have DataObject representation");
     }
 
     @Override
-    public NormalizedNode<?, ?> serialize(D data) {
+    public NormalizedNode<?, ?> serialize(final D data) {
         throw new UnsupportedOperationException("Separete serialization of leaf node is not supported.");
     }
 
     @Override
-    public void writeAsNormalizedNode(D data, NormalizedNodeStreamWriter writer) {
+    public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) {
         throw new UnsupportedOperationException("Separete serialization of leaf node is not supported.");
     }
 
     @Override
-    public <E extends DataObject> BindingCodecTreeNode<E> streamChild(Class<E> childClass) {
-        return null;
+    public <E extends DataObject> BindingCodecTreeNode<E> streamChild(final Class<E> childClass) {
+        throw new IllegalArgumentException("Leaf does not have children");
     }
 
     @Override
     public <E extends DataObject> Optional<? extends BindingCodecTreeNode<E>> possibleStreamChild(
-            Class<E> childClass) {
-        return null;
+            final Class<E> childClass) {
+        throw new IllegalArgumentException("Leaf does not have children");
     }
 
     @Override
-    public BindingCodecTreeNode<?> yangPathArgumentChild(
-            org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument child) {
-        return null;
+    public BindingCodecTreeNode<?> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument child) {
+        throw new IllegalArgumentException("Leaf does not have children");
     }
 
     @Override
-    protected Object deserializeObject(NormalizedNode<?, ?> normalizedNode) {
+    protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
         if (normalizedNode instanceof LeafNode<?>) {
             return valueCodec.deserialize(normalizedNode.getValue());
         } else if(normalizedNode instanceof LeafSetNode<?>) {
             @SuppressWarnings("unchecked")
-            Collection<LeafSetEntryNode<Object>> domValues = ((LeafSetNode<Object>) normalizedNode).getValue();
-            List<Object> result = new ArrayList<>(domValues.size());
-            for (LeafSetEntryNode<Object> valueNode : domValues) {
+            final Collection<LeafSetEntryNode<Object>> domValues = ((LeafSetNode<Object>) normalizedNode).getValue();
+            final List<Object> result = new ArrayList<>(domValues.size());
+            for (final LeafSetEntryNode<Object> valueNode : domValues) {
                 result.add(valueCodec.deserialize(valueNode.getValue()));
             }
             return result;
@@ -124,13 +123,13 @@ final class LeafNodeCodecContext<D extends DataObject> extends NodeCodecContext<
     }
 
     @Override
-    public InstanceIdentifier.PathArgument deserializePathArgument(YangInstanceIdentifier.PathArgument arg) {
+    public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
         Preconditions.checkArgument(getDomPathArgument().equals(arg));
         return null;
     }
 
     @Override
-    public YangInstanceIdentifier.PathArgument serializePathArgument(InstanceIdentifier.PathArgument arg) {
+    public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) {
         return getDomPathArgument();
     }
 
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaException.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaException.java
new file mode 100644 (file)
index 0000000..5c5bac6
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import com.google.common.base.Preconditions;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Thrown when codec was used with data which are not modeled
+ * and available in schema used by codec.
+ *
+ */
+public class MissingSchemaException extends IllegalArgumentException {
+
+    private static final long serialVersionUID = 1L;
+
+    protected MissingSchemaException(final String msg) {
+        super(msg);
+    }
+
+    private static MissingSchemaException create(final String format, final Object... args) {
+        return new MissingSchemaException(String.format(format, args));
+    }
+
+    static void checkModulePresent(final SchemaContext schemaContext, final QName name) {
+        if(schemaContext.findModuleByNamespaceAndRevision(name.getNamespace(), name.getRevision()) == null) {
+            throw MissingSchemaException.create("Module %s is not present in current schema context.",name.getModule());
+        }
+    }
+
+    static void checkModulePresent(final SchemaContext schemaContext, final YangInstanceIdentifier.PathArgument child) {
+        checkModulePresent(schemaContext, extractName(child));
+    }
+
+    private static QName extractName(final PathArgument child) {
+        if(child instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+            final Set<QName> children = ((YangInstanceIdentifier.AugmentationIdentifier) child).getPossibleChildNames();
+            Preconditions.checkArgument(!children.isEmpty(),"Augmentation without childs must not be used in data");
+            return children.iterator().next();
+        }
+        return child.getNodeType();
+    }
+
+
+
+
+}
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaForClassException.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/MissingSchemaForClassException.java
new file mode 100644 (file)
index 0000000..6f9778f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
+import org.opendaylight.yangtools.yang.binding.Augmentation;
+
+/**
+ *
+ * Thrown when Java Binding class was used in data for which codec does not
+ * have schema.
+ *
+ * By serialization /  deserialization of this exception {@link #getBindingClass()}
+ * will return null.
+ *
+ */
+public class MissingSchemaForClassException extends MissingSchemaException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final transient Class<?> bindingClass;
+
+    private MissingSchemaForClassException(final Class<?> clz) {
+        super(String.format("Schema is not available for %s", clz));
+        this.bindingClass = Preconditions.checkNotNull(clz);
+    }
+
+    static MissingSchemaForClassException forClass(final Class<?> clz) {
+        return new MissingSchemaForClassException(clz);
+    }
+
+    public Class<?> getBindingClass() {
+        return bindingClass;
+    }
+
+    public static void check(final BindingRuntimeContext runtimeContext, final Class<?> bindingClass) {
+        final Object schema;
+        if (Augmentation.class.isAssignableFrom(bindingClass)) {
+            schema = runtimeContext.getAugmentationDefinition(bindingClass);
+        } else {
+            schema = runtimeContext.getSchemaDefinition(bindingClass);
+        }
+        if(schema == null) {
+            throw MissingSchemaForClassException.forClass(bindingClass);
+        }
+    }
+}
index b2fb7d0867a7cf5a470121819e4661546d66017c..16533f06a42647d471d4a999ec69302527771f77 100644 (file)
@@ -9,9 +9,11 @@ package org.opendaylight.yangtools.binding.data.codec.impl;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.UncheckedExecutionException;
 import org.opendaylight.yangtools.util.ClassLoaderUtils;
 import org.opendaylight.yangtools.yang.binding.BindingMapping;
 import org.opendaylight.yangtools.yang.binding.ChildOf;
@@ -70,9 +72,7 @@ final class SchemaRootCodecContext<D extends DataObject> extends DataContainerCo
                 @Override
                 public DataContainerCodecContext<?,?> load(final QName qname) {
                     final DataSchemaNode childSchema = schema().getDataChildByName(qname);
-                    Preconditions.checkArgument(childSchema != null, "Argument %s is not valid child of %s", qname,
-                            schema());
-
+                    childNonNull(childSchema, qname,"Argument %s is not valid child of %s", qname,schema());
                     if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) {
                         @SuppressWarnings("rawtypes")
                         final Class childCls = factory().getRuntimeContext().getClassForSchema(childSchema);
@@ -126,7 +126,7 @@ final class SchemaRootCodecContext<D extends DataObject> extends DataContainerCo
 
     @SuppressWarnings("unchecked")
     @Override
-    public <DV extends DataObject> DataContainerCodecContext<DV, ?> streamChild(Class<DV> childClass)
+    public <DV extends DataObject> DataContainerCodecContext<DV, ?> streamChild(final Class<DV> childClass)
             throws IllegalArgumentException {
         /* FIXME: This is still not solved for RPCs
          * TODO: Probably performance wise RPC, Data and Notification loading cache
@@ -136,7 +136,7 @@ final class SchemaRootCodecContext<D extends DataObject> extends DataContainerCo
         if (Notification.class.isAssignableFrom(childClass)) {
             return (DataContainerCodecContext<DV, ?>) getNotification((Class<? extends Notification>)childClass);
         }
-        return (DataContainerCodecContext<DV, ?>) childrenByClass.getUnchecked(childClass);
+        return (DataContainerCodecContext<DV, ?>) getOrRethrow(childrenByClass,childClass);
     }
 
     @Override
@@ -146,7 +146,7 @@ final class SchemaRootCodecContext<D extends DataObject> extends DataContainerCo
 
     @Override
     public DataContainerCodecContext<?,?> yangPathArgumentChild(final PathArgument arg) {
-        return childrenByQName.getUnchecked(arg.getNodeType());
+        return getOrRethrow(childrenByQName,arg.getNodeType());
     }
 
     @Override
@@ -157,26 +157,26 @@ final class SchemaRootCodecContext<D extends DataObject> extends DataContainerCo
 
 
     ContainerNodeCodecContext<?> getRpc(final Class<? extends DataContainer> rpcInputOrOutput) {
-        return rpcDataByClass.getUnchecked(rpcInputOrOutput);
+        return getOrRethrow(rpcDataByClass, rpcInputOrOutput);
     }
 
     NotificationCodecContext<?> getNotification(final Class<? extends Notification> notification) {
-        return notificationsByClass.getUnchecked(notification);
+        return getOrRethrow(notificationsByClass, notification);
     }
 
     NotificationCodecContext<?> getNotification(final SchemaPath notification) {
-        return notificationsByPath.getUnchecked(notification);
+        return getOrRethrow(notificationsByPath, notification);
     }
 
     ContainerNodeCodecContext<?> getRpc(final SchemaPath notification) {
-        return rpcDataByPath.getUnchecked(notification);
+        return getOrRethrow(rpcDataByPath, notification);
     }
 
     private DataContainerCodecContext<?,?> createDataTreeChildContext(final Class<?> key) {
         final Class<Object> parent = ClassLoaderUtils.findFirstGenericArgument(key, ChildOf.class);
-        Preconditions.checkArgument(DataRoot.class.isAssignableFrom(parent));
+        IncorrectNestingException.check(DataRoot.class.isAssignableFrom(parent), "Class %s is not top level item.", key);
         final QName qname = BindingReflections.findQName(key);
-        final DataSchemaNode childSchema = schema().getDataChildByName(qname);
+        final DataSchemaNode childSchema = childNonNull(schema().getDataChildByName(qname),key,"%s is not top-level item.",key);
         return DataContainerCodecPrototype.from(key, childSchema, factory()).get();
     }
 
@@ -226,20 +226,32 @@ final class SchemaRootCodecContext<D extends DataObject> extends DataContainerCo
     }
 
     @Override
-    protected Object deserializeObject(NormalizedNode<?, ?> normalizedNode) {
+    protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
         throw new UnsupportedOperationException("Unable to deserialize root");
     }
 
     @Override
-    public InstanceIdentifier.PathArgument deserializePathArgument(YangInstanceIdentifier.PathArgument arg) {
+    public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
         Preconditions.checkArgument(arg == null);
         return null;
     }
 
     @Override
-    public YangInstanceIdentifier.PathArgument serializePathArgument(InstanceIdentifier.PathArgument arg) {
+    public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) {
         Preconditions.checkArgument(arg == null);
         return null;
     }
 
+    private static <K,V> V getOrRethrow(final LoadingCache<K, V> cache, final K key) {
+        try {
+            return cache.getUnchecked(key);
+        } catch (final UncheckedExecutionException e) {
+            final Throwable cause = e.getCause();
+            if(cause != null) {
+                Throwables.propagateIfPossible(cause);
+            }
+            throw e;
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/ExceptionReportingTest.java b/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/ExceptionReportingTest.java
new file mode 100644 (file)
index 0000000..4a565ed
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2015 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 javassist.ClassPool;
+import org.junit.Test;
+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.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.TopLevelListKey;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.yangtools.binding.data.codec.impl.IncorrectNestingException;
+import org.opendaylight.yangtools.binding.data.codec.impl.MissingSchemaException;
+import org.opendaylight.yangtools.binding.data.codec.impl.MissingSchemaForClassException;
+import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
+import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
+import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+
+public class ExceptionReportingTest {
+
+
+    private static final BindingNormalizedNodeCodecRegistry EMPTY_SCHEMA_CODEC = codec();
+    private static final BindingNormalizedNodeCodecRegistry ONLY_TOP_CODEC = codec(Top.class);
+    private static final BindingNormalizedNodeCodecRegistry FULL_CODEC = codec(TreeComplexUsesAugment.class);
+
+    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).build();
+    private static final InstanceIdentifier<TreeLeafOnlyAugment> BA_TREE_LEAF_ONLY =
+            BA_TOP_LEVEL_LIST.augmentation(TreeLeafOnlyAugment.class);
+
+    private static final QName TOP_QNAME =
+            QName.create("urn:opendaylight:params:xml:ns:yang:yangtools:test:binding", "2014-07-01", "top");
+    private static final YangInstanceIdentifier BI_TOP_PATH = YangInstanceIdentifier.builder().node(TOP_QNAME).build();
+    private static final YangInstanceIdentifier BI_TREE_LEAF_ONLY = FULL_CODEC.toYangInstanceIdentifier(BA_TREE_LEAF_ONLY);
+
+
+    @Test(expected=MissingSchemaException.class)
+    public void testDOMTop() {
+        EMPTY_SCHEMA_CODEC.fromYangInstanceIdentifier(BI_TOP_PATH);
+    }
+
+    @Test(expected=MissingSchemaException.class)
+    public void testDOMAugment() {
+        EMPTY_SCHEMA_CODEC.fromYangInstanceIdentifier(BI_TREE_LEAF_ONLY);
+    }
+
+    @Test(expected=MissingSchemaForClassException.class)
+    public void testBindingTop() {
+        EMPTY_SCHEMA_CODEC.toYangInstanceIdentifier(BA_TOP_LEVEL_LIST);
+    }
+
+    @Test(expected=MissingSchemaForClassException.class)
+    public void testBindingAugment() {
+        ONLY_TOP_CODEC.toYangInstanceIdentifier(BA_TREE_LEAF_ONLY);
+    }
+
+    @Test(expected=IncorrectNestingException.class)
+    public void testBindingSkippedRoot() {
+        FULL_CODEC.toYangInstanceIdentifier(InstanceIdentifier.create(TopLevelList.class));
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    @Test(expected=IncorrectNestingException.class)
+    public void testBindingIncorrectAugment() {
+        FULL_CODEC.toYangInstanceIdentifier(InstanceIdentifier.create(Top.class).augmentation((Class) TreeComplexUsesAugment.class));
+    }
+
+
+    private static BindingNormalizedNodeCodecRegistry codec(final Class<?>... classes ) {
+        final ModuleInfoBackedContext ctx = ModuleInfoBackedContext.create();
+        for(final Class<?> clazz : classes) {
+            YangModuleInfo modInfo;
+            try {
+                modInfo = BindingReflections.getModuleInfo(clazz);
+                ctx.registerModuleInfo(modInfo);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e);
+            }
+        }
+        final SchemaContext schema = ctx.tryToCreateSchemaContext().get();
+        final BindingRuntimeContext runtimeCtx = BindingRuntimeContext.create(ctx, schema);
+        final JavassistUtils utils = JavassistUtils.forClassPool(ClassPool.getDefault());
+        final BindingNormalizedNodeCodecRegistry registry = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(utils));
+        registry.onBindingRuntimeContextUpdated(runtimeCtx);
+        return registry;
+    }
+
+}
index 0db4b7da8f89af4af8ffefdc3e4506c1775813dc..e92f1ca0d0562718b78effe222897276b3e2b12f 100644 (file)
@@ -26,6 +26,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import javax.annotation.Nullable;
 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy;
@@ -40,7 +41,6 @@ import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTy
 import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.AugmentationSchemaProxy;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
@@ -51,6 +51,7 @@ import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
 import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -163,14 +164,12 @@ public class BindingRuntimeContext implements Immutable {
      * schema.
      *
      * @param augClass Augmentation class
-     * @return Schema of augmentation
-     * @throws IllegalArgumentException If supplied class is not an augmentation or current context does not contain schema for augmentation.
+     * @return Schema of augmentation or null if augmentaiton is not known in this context
+     * @throws IllegalArgumentException If supplied class is not an augmentation
      */
-    public AugmentationSchema getAugmentationDefinition(final Class<?> augClass) throws IllegalArgumentException {
+    public @Nullable AugmentationSchema getAugmentationDefinition(final Class<?> augClass) throws IllegalArgumentException {
         Preconditions.checkArgument(Augmentation.class.isAssignableFrom(augClass), "Class %s does not represent augmentation", augClass);
-        final AugmentationSchema ret = augmentationToSchema.get(referencedType(augClass));
-        Preconditions.checkArgument(ret != null, "Supplied augmentation %s is not valid in current context", augClass);
-        return ret;
+        return augmentationToSchema.get(referencedType(augClass));
     }
 
     /**
@@ -195,6 +194,7 @@ public class BindingRuntimeContext implements Immutable {
     public Entry<AugmentationIdentifier, AugmentationSchema> getResolvedAugmentationSchema(final DataNodeContainer target,
             final Class<? extends Augmentation<?>> aug) {
         final AugmentationSchema origSchema = getAugmentationDefinition(aug);
+        Preconditions.checkArgument(origSchema != null, "Augmentation %s is not known in current schema context",aug);
         /*
          * FIXME: Validate augmentation schema lookup
          *
@@ -215,7 +215,7 @@ public class BindingRuntimeContext implements Immutable {
         }
 
         final AugmentationIdentifier identifier = new AugmentationIdentifier(childNames);
-        final AugmentationSchema proxy = new AugmentationSchemaProxy(origSchema, realChilds);
+        final AugmentationSchema proxy = new EffectiveAugmentationSchema(origSchema, realChilds);
         return new AbstractMap.SimpleEntry<>(identifier, proxy);
     }