Bug 3067: Improved error reporting in Binding Data Codec
[mdsal.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / DataObjectCodecContext.java
index 5725e186ba0be23d12bf88251e4fc6e79e5daa8d..0ca1d2b8504d3183bb01783312eb1ea4717dcc23 100644 (file)
@@ -9,9 +9,16 @@ 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.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedMap;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -24,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;
@@ -42,85 +50,88 @@ import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-abstract class DataObjectCodecContext<T extends DataNodeContainer> extends DataContainerCodecContext<T> {
+abstract class DataObjectCodecContext<D extends DataObject,T extends DataNodeContainer> extends DataContainerCodecContext<D,T> {
     private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class);
-
+    private static final Lookup LOOKUP = MethodHandles.publicLookup();
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class);
+    private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, InvocationHandler.class);
     private static final Comparator<Method> METHOD_BY_ALPHABET = new Comparator<Method>() {
-
         @Override
         public int compare(final Method o1, final Method o2) {
             return o1.getName().compareTo(o2.getName());
         }
     };
 
-    private final ImmutableMap<String, LeafNodeCodecContext> leafChild;
+    private final ImmutableMap<String, LeafNodeCodecContext<?>> leafChild;
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
     private final ImmutableSortedMap<Method, NodeContextSupplier> byMethod;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
-    protected final Method augmentationGetter;
+    private final MethodHandle proxyConstructor;
 
     protected DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype) {
         super(prototype);
 
-        this.leafChild = factory().getLeafNodes(bindingClass(), schema());
+        this.leafChild = factory().getLeafNodes(getBindingClass(), schema());
 
-        Map<Class<?>, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(bindingClass());
+        final Map<Class<?>, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(getBindingClass());
 
-        Map<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYangBuilder = new HashMap<>();
-        SortedMap<Method, NodeContextSupplier> byMethodBuilder = new TreeMap<>(METHOD_BY_ALPHABET);
-        Map<Class<?>, DataContainerCodecPrototype<?>> byStreamClassBuilder = new HashMap<>();
-        Map<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClassBuilder = new HashMap<>();
+        final Map<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYangBuilder = new HashMap<>();
+        final SortedMap<Method, NodeContextSupplier> byMethodBuilder = new TreeMap<>(METHOD_BY_ALPHABET);
+        final Map<Class<?>, DataContainerCodecPrototype<?>> byStreamClassBuilder = new HashMap<>();
+        final Map<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClassBuilder = new HashMap<>();
 
         // Adds leaves to mapping
-        for (LeafNodeCodecContext leaf : leafChild.values()) {
+        for (final LeafNodeCodecContext<?> leaf : leafChild.values()) {
             byMethodBuilder.put(leaf.getGetter(), leaf);
             byYangBuilder.put(leaf.getDomPathArgument(), leaf);
         }
 
-        for (Entry<Class<?>, Method> childDataObj : clsToMethod.entrySet()) {
-            DataContainerCodecPrototype<?> childProto = loadChildPrototype(childDataObj.getKey());
+        for (final Entry<Class<?>, Method> childDataObj : clsToMethod.entrySet()) {
+            final DataContainerCodecPrototype<?> childProto = loadChildPrototype(childDataObj.getKey());
             byMethodBuilder.put(childDataObj.getValue(), childProto);
             byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
             byYangBuilder.put(childProto.getYangArg(), childProto);
             if (childProto.isChoice()) {
-                ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) childProto.get();
-                for(Class<?> cazeChild : choice.getCaseChildrenClasses()) {
+                final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) childProto.get();
+                for(final Class<?> cazeChild : choice.getCaseChildrenClasses()) {
                     byBindingArgClassBuilder.put(cazeChild, childProto);
                 }
             }
         }
         this.byMethod = ImmutableSortedMap.copyOfSorted(byMethodBuilder);
-        if (Augmentable.class.isAssignableFrom(bindingClass())) {
-            try {
-                augmentationGetter = bindingClass().getMethod("getAugmentation", Class.class);
-            } catch (NoSuchMethodException | SecurityException e) {
-               throw new IllegalStateException("Could not get required method.",e);
-            }
-            ImmutableMap<AugmentationIdentifier, Type> augmentations = factory().getRuntimeContext()
+        if (Augmentable.class.isAssignableFrom(getBindingClass())) {
+            final ImmutableMap<AugmentationIdentifier, Type> augmentations = factory().getRuntimeContext()
                     .getAvailableAugmentationTypes(schema());
-            for (Entry<AugmentationIdentifier, Type> augment : augmentations.entrySet()) {
-                DataContainerCodecPrototype<?> augProto = getAugmentationPrototype(augment.getValue());
+            for (final Entry<AugmentationIdentifier, Type> augment : augmentations.entrySet()) {
+                final DataContainerCodecPrototype<?> augProto = getAugmentationPrototype(augment.getValue());
                 if (augProto != null) {
                     byYangBuilder.put(augProto.getYangArg(), augProto);
                     byStreamClassBuilder.put(augProto.getBindingClass(), augProto);
                 }
             }
-        } else {
-            augmentationGetter = null;
         }
 
         this.byYang = ImmutableMap.copyOf(byYangBuilder);
         this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
         byBindingArgClassBuilder.putAll(byStreamClass);
         this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
+
+        final Class<?> proxyClass = Proxy.getProxyClass(getBindingClass().getClassLoader(),  new Class[] { getBindingClass(), AugmentationHolder.class });
+        try {
+            proxyConstructor = LOOKUP.findConstructor(proxyClass, CONSTRUCTOR_TYPE).asType(DATAOBJECT_TYPE);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new IllegalStateException("Failed to find contructor for class " + proxyClass);
+        }
     }
 
+
+    @SuppressWarnings("unchecked")
     @Override
-    protected DataContainerCodecContext<?> getStreamChild(final Class<?> childClass) {
+    public <DV extends DataObject> DataContainerCodecContext<DV, ?> streamChild(final Class<DV> childClass) {
         DataContainerCodecPrototype<?> childProto = byStreamClass.get(childClass);
         if (childProto != null) {
-            return childProto.get();
+            return (DataContainerCodecContext<DV,?>) childProto.get();
         }
 
         if (Augmentation.class.isAssignableFrom(childClass))  {
@@ -131,9 +142,10 @@ abstract class DataObjectCodecContext<T extends DataNodeContainer> extends DataC
              *
              * FIXME: Cache mapping of mismatched augmentation to real one, to speed up lookup.
              */
-            Class<?> augTarget = BindingReflections.findAugmentationTarget((Class) childClass);
-            if ((bindingClass().equals(augTarget))) {
-                for (DataContainerCodecPrototype<?> realChild : byStreamClass.values()) {
+            @SuppressWarnings("rawtypes")
+            final Class<?> augTarget = BindingReflections.findAugmentationTarget((Class) childClass);
+            if ((getBindingClass().equals(augTarget))) {
+                for (final DataContainerCodecPrototype<?> realChild : byStreamClass.values()) {
                     if (Augmentation.class.isAssignableFrom(realChild.getBindingClass())
                             && BindingReflections.isSubstitutionFor(childClass,realChild.getBindingClass())) {
                         childProto = realChild;
@@ -142,63 +154,64 @@ abstract class DataObjectCodecContext<T extends DataNodeContainer> extends DataC
                 }
             }
         }
-        Preconditions.checkArgument(childProto != null, " Child %s is not valid child.",childClass);
-        return childProto.get();
+        return (DataContainerCodecContext<DV, ?>) childNonNull(childProto, childClass, " Child %s is not valid child.").get();
     }
 
+
+    @SuppressWarnings("unchecked")
     @Override
-    protected Optional<DataContainerCodecContext<?>> getPossibleStreamChild(final Class<?> childClass) {
-        DataContainerCodecPrototype<?> childProto = byStreamClass.get(childClass);
+    public <DV extends DataObject> Optional<DataContainerCodecContext<DV, ?>> possibleStreamChild(
+            final Class<DV> childClass) {
+        final DataContainerCodecPrototype<?> childProto = byStreamClass.get(childClass);
         if(childProto != null) {
-            return Optional.<DataContainerCodecContext<?>>of(childProto.get());
+            return Optional.<DataContainerCodecContext<DV,?>>of((DataContainerCodecContext<DV,?>) childProto.get());
         }
         return Optional.absent();
     }
 
     @Override
-    protected DataContainerCodecContext<?> getIdentifierChild(final InstanceIdentifier.PathArgument arg,
+    public DataContainerCodecContext<?,?> bindingPathArgumentChild(final InstanceIdentifier.PathArgument arg,
             final List<YangInstanceIdentifier.PathArgument> builder) {
 
-        Class<? extends DataObject> argType = arg.getType();
-        DataContainerCodecPrototype<?> ctxProto = byBindingArgClass.get(argType);
-        Preconditions.checkArgument(ctxProto != null,"Invalid child");
-
-        DataContainerCodecContext<?> context = ctxProto.get();
-        if(context instanceof ChoiceNodeCodecContext) {
-            ChoiceNodeCodecContext casted = (ChoiceNodeCodecContext) context;
-            casted.addYangPathArgument(arg, builder);
-            DataContainerCodecContext<?> caze = casted.getCazeByChildClass(arg.getType());
+        final Class<? extends DataObject> argType = arg.getType();
+        final DataContainerCodecPrototype<?> ctxProto = byBindingArgClass.get(argType);
+        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.getIdentifierChild(arg, builder);
+            return caze.bindingPathArgumentChild(arg, builder);
         }
         context.addYangPathArgument(arg, builder);
         return context;
     }
 
+    @SuppressWarnings("unchecked")
     @Override
-    protected NodeCodecContext getYangIdentifierChild(YangInstanceIdentifier.PathArgument arg) {
+    public NodeCodecContext<D> yangPathArgumentChild(YangInstanceIdentifier.PathArgument arg) {
         if(arg instanceof NodeIdentifierWithPredicates) {
             arg = new NodeIdentifier(arg.getNodeType());
         }
-        NodeContextSupplier childSupplier = byYang.get(arg);
-        Preconditions.checkArgument(childSupplier != null, "Argument %s is not valid child of %s", arg, schema());
-        return childSupplier.get();
+        final NodeContextSupplier childSupplier = byYang.get(arg);
+        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, bindingClass());
-        return value;
+    protected final LeafNodeCodecContext<?> getLeafChild(final String name) {
+        final LeafNodeCodecContext<?> value = leafChild.get(name);
+        return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass());
     }
 
     private DataContainerCodecPrototype<?> loadChildPrototype(final Class<?> childClass) {
-        DataSchemaNode origDef = factory().getRuntimeContext().getSchemaDefinition(childClass);
+        final DataSchemaNode origDef = factory().getRuntimeContext().getSchemaDefinition(childClass);
         // Direct instantiation or use in same module in which grouping
         // was defined.
         DataSchemaNode sameName;
         try {
             sameName = schema().getDataChildByName(origDef.getQName());
-        } catch (IllegalArgumentException e) {
+        } catch (final IllegalArgumentException e) {
             sameName = null;
         }
         final DataSchemaNode childSchema;
@@ -216,8 +229,8 @@ abstract class DataObjectCodecContext<T extends DataNodeContainer> extends DataC
             }
         } else {
             // We are looking for instantiation via uses in other module
-            QName instantiedName = QName.create(namespace(), origDef.getQName().getLocalName());
-            DataSchemaNode potential = schema().getDataChildByName(instantiedName);
+            final QName instantiedName = QName.create(namespace(), origDef.getQName().getLocalName());
+            final DataSchemaNode potential = schema().getDataChildByName(instantiedName);
             // We check if it is really instantiated from same
             // definition as class was derived
             if (potential != null && origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential))) {
@@ -226,47 +239,59 @@ abstract class DataObjectCodecContext<T extends DataNodeContainer> extends DataC
                 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) {
-        ClassLoadingStrategy loader = factory().getRuntimeContext().getStrategy();
+        final ClassLoadingStrategy loader = factory().getRuntimeContext().getStrategy();
         @SuppressWarnings("rawtypes")
         final Class augClass;
         try {
             augClass = loader.loadClass(value);
-        } catch (ClassNotFoundException e) {
+        } catch (final ClassNotFoundException e) {
             LOG.warn("Failed to load augmentation prototype for {}", value, e);
             return null;
         }
 
-        Entry<AugmentationIdentifier, AugmentationSchema> augSchema = factory().getRuntimeContext()
+        @SuppressWarnings("unchecked")
+        final Entry<AugmentationIdentifier, AugmentationSchema> augSchema = factory().getRuntimeContext()
                 .getResolvedAugmentationSchema(schema(), augClass);
         return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory());
     }
 
     @SuppressWarnings("rawtypes")
     Object getBindingChildValue(final Method method, final NormalizedNodeContainer domData) {
-        NodeCodecContext childContext = byMethod.get(method).get();
-        Optional<NormalizedNode<?, ?>> domChild = domData.getChild(childContext.getDomPathArgument());
+        final NodeCodecContext<?> childContext = byMethod.get(method).get();
+        @SuppressWarnings("unchecked")
+        final Optional<NormalizedNode<?, ?>> domChild = domData.getChild(childContext.getDomPathArgument());
         if (domChild.isPresent()) {
-            return childContext.dataFromNormalizedNode(domChild.get());
+            return childContext.deserializeObject(domChild.get());
         }
         return null;
     }
 
+    protected final D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
+        try {
+            return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node));
+        } catch (final Throwable e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
     public Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentationsFrom(
             final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data) {
 
         @SuppressWarnings("rawtypes")
-        Map map = new HashMap<>();
+        final Map map = new HashMap<>();
 
-        for(DataContainerCodecPrototype<?> value : byStreamClass.values()) {
+        for(final DataContainerCodecPrototype<?> value : byStreamClass.values()) {
             if(Augmentation.class.isAssignableFrom(value.getBindingClass())) {
-                Optional<NormalizedNode<?, ?>> augData = data.getChild(value.getYangArg());
+                final Optional<NormalizedNode<?, ?>> augData = data.getChild(value.getYangArg());
                 if(augData.isPresent()) {
-                    map.put(value.getBindingClass(), value.get().dataFromNormalizedNode(augData.get()));
+                    map.put(value.getBindingClass(), value.get().deserializeObject(augData.get()));
                 }
             }
         }
@@ -278,4 +303,15 @@ abstract class DataObjectCodecContext<T extends DataNodeContainer> extends DataC
         return byMethod.keySet();
     }
 
+    @Override
+    public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
+        Preconditions.checkArgument(getDomPathArgument().equals(arg));
+        return bindingArg();
+    }
+
+    @Override
+    public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) {
+        Preconditions.checkArgument(bindingArg().equals(arg));
+        return getDomPathArgument();
+    }
 }