Merge "BUG-869: added proper handling of nullable parameter"
[yangtools.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / BindingCodecContext.java
index cff89a655000798bdf5880a61dc964176d00f275..6d1e31c2b47a1a3b7589ee060f89cbcd39156633 100644 (file)
@@ -9,6 +9,8 @@ package org.opendaylight.yangtools.binding.data.codec.impl;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -16,12 +18,17 @@ import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
 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;
@@ -49,20 +56,22 @@ import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
 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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 class BindingCodecContext implements CodecContextFactory, Immutable {
-
+    private static final Logger LOG = LoggerFactory.getLogger(BindingCodecContext.class);
     private static final String GETTER_PREFIX = "get";
+
     private final SchemaRootCodecContext root;
     private final BindingRuntimeContext context;
-    private final Codec<YangInstanceIdentifier, InstanceIdentifier<?>> instanceIdentifierCodec;
-    private final Codec<QName, Class<?>> identityCodec;
+    private final Codec<YangInstanceIdentifier, InstanceIdentifier<?>> instanceIdentifierCodec =
+            new InstanceIdentifierCodec();
+    private final Codec<QName, Class<?>> identityCodec = new IdentityCodec();
 
     public BindingCodecContext(final BindingRuntimeContext context) {
         this.context = Preconditions.checkNotNull(context, "Binding Runtime Context is required.");
         this.root = SchemaRootCodecContext.create(this);
-        this.instanceIdentifierCodec = new InstanceIdentifierCodec();
-        this.identityCodec = new IdentityCodec();
     }
 
     @Override
@@ -70,7 +79,7 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
         return context;
     }
 
-    public Codec<YangInstanceIdentifier, InstanceIdentifier<?>> getInstanceIdentifierCodec() {
+    Codec<YangInstanceIdentifier, InstanceIdentifier<?>> getInstanceIdentifierCodec() {
         return instanceIdentifierCodec;
     }
 
@@ -100,14 +109,27 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
         return currentNode;
     }
 
-    public NodeCodecContext getCodecContextNode(final YangInstanceIdentifier dom,
-            final List<InstanceIdentifier.PathArgument> builder) {
+    /**
+     * Multi-purpose utility function. Traverse the codec tree, looking for
+     * the appropriate codec for the specified {@link YangInstanceIdentifier}.
+     * As a side-effect, gather all traversed binding {@link InstanceIdentifier.PathArgument}s
+     * into the supplied collection.
+     *
+     * @param dom {@link YangInstanceIdentifier} which is to be translated
+     * @param bindingArguments Collection for traversed path arguments
+     * @return Codec for target node, or @null if the node does not have a
+     *         binding representation (choice, case, leaf).
+     */
+    @Nullable NodeCodecContext getCodecContextNode(final @Nonnull YangInstanceIdentifier dom,
+            final @Nonnull Collection<InstanceIdentifier.PathArgument> bindingArguments) {
         NodeCodecContext currentNode = root;
         ListNodeCodecContext currentList = null;
+
         for (YangInstanceIdentifier.PathArgument domArg : dom.getPathArguments()) {
-            Preconditions.checkArgument(currentNode instanceof DataContainerCodecContext<?>);
-            DataContainerCodecContext<?> previous = (DataContainerCodecContext<?>) currentNode;
-            NodeCodecContext nextNode = previous.getYangIdentifierChild(domArg);
+            Preconditions.checkArgument(currentNode instanceof DataContainerCodecContext<?>, "Unexpected child of non-container node %s", currentNode);
+            final DataContainerCodecContext<?> previous = (DataContainerCodecContext<?>) currentNode;
+            final NodeCodecContext nextNode = previous.getYangIdentifierChild(domArg);
+
             /*
              * List representation in YANG Instance Identifier consists of two
              * arguments: first is list as a whole, second is list as an item so
@@ -117,19 +139,13 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
              * Identifier as Item or IdentifiableItem
              */
             if (currentList != null) {
+                Preconditions.checkArgument(currentList == nextNode, "List should be referenced two times in YANG Instance Identifier %s", dom);
 
-                if (currentList == nextNode) {
-
-                    // We entered list, so now we have all information to emit
-                    // list
-                    // path using second list argument.
-                    builder.add(currentList.getBindingPathArgument(domArg));
-                    currentList = null;
-                    currentNode = nextNode;
-                } else {
-                    throw new IllegalArgumentException(
-                            "List should be referenced two times in YANG Instance Identifier");
-                }
+                // We entered list, so now we have all information to emit
+                // list path using second list argument.
+                bindingArguments.add(currentList.getBindingPathArgument(domArg));
+                currentList = null;
+                currentNode = nextNode;
             } else if (nextNode instanceof ListNodeCodecContext) {
                 // We enter list, we do not update current Node yet,
                 // since we need to verify
@@ -139,24 +155,27 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
                 // it is not supported by binding instance identifier.
                 currentNode = nextNode;
             } else if (nextNode instanceof DataContainerCodecContext<?>) {
-                builder.add(((DataContainerCodecContext<?>) nextNode).getBindingPathArgument(domArg));
+                bindingArguments.add(((DataContainerCodecContext<?>) nextNode).getBindingPathArgument(domArg));
                 currentNode = nextNode;
             } else if (nextNode instanceof LeafNodeCodecContext) {
-                Preconditions.checkArgument(builder == null, "Instance Identifier for leaf is not representable.");
-
+                LOG.debug("Instance identifier referencing a leaf is not representable (%s)", dom);
+                return null;
             }
         }
+
         // Algorithm ended in list as whole representation
         // we sill need to emit identifier for list
-
-        if (builder != null) {
-            Preconditions.checkArgument(!(currentNode instanceof ChoiceNodeCodecContext),
-                    "Instance Identifier for choice is not representable.");
-            Preconditions.checkArgument(!(currentNode instanceof CaseNodeCodecContext),
-                    "Instance Identifier for case is not representable.");
+        if (currentNode instanceof ChoiceNodeCodecContext) {
+            LOG.debug("Instance identifier targeting a choice is not representable (%s)", dom);
+            return null;
+        }
+        if (currentNode instanceof CaseNodeCodecContext) {
+            LOG.debug("Instance identifier targeting a case is not representable (%s)", dom);
+            return null;
         }
+
         if (currentList != null) {
-            builder.add(currentList.getBindingPathArgument(null));
+            bindingArguments.add(currentList.getBindingPathArgument(null));
             return currentList;
         }
         return currentNode;
@@ -216,16 +235,15 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
                 } else {
                     continue; // We do not have schema for leaf, so we will ignore it (eg. getClass, getImplementedInterface).
                 }
-                final LeafNodeCodecContext leafNode = leafNodeFrom(valueType, schema);
+                Codec<Object, Object> codec = getCodec(valueType, schema);
+                final LeafNodeCodecContext leafNode = new LeafNodeCodecContext(schema, codec, method);
                 leaves.put(schema.getQName().getLocalName(), leafNode);
             }
         }
         return ImmutableMap.copyOf(leaves);
     }
 
-    private LeafNodeCodecContext leafNodeFrom(final Class<?> returnType, final DataSchemaNode schema) {
-        return new LeafNodeCodecContext(schema, getCodec(returnType, schema));
-    }
+
 
     private Codec<Object, Object> getCodec(final Class<?> valueType, final DataSchemaNode schema) {
         if (Class.class.equals(valueType)) {
@@ -263,12 +281,11 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
         } else if (rootType instanceof InstanceIdentifierTypeDefinition) {
             return ValueTypeCodec.encapsulatedValueCodecFor(valueType, instanceIdentifierCodec);
         } else if (rootType instanceof UnionTypeDefinition) {
-            Callable<UnionTypeCodec> loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) rootType,
-                    getInstanceIdentifierCodec(), getIdentityCodec());
+            Callable<UnionTypeCodec> loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) rootType);
             try {
                 return loader.call();
             } catch (Exception e) {
-                throw new IllegalStateException("Unable to load codec for "+valueType,e);
+                throw new IllegalStateException("Unable to load codec for " + valueType, e);
             }
         }
         return ValueTypeCodec.getCodecFor(valueType, instantiatedType);
@@ -278,16 +295,16 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
 
         @Override
         public YangInstanceIdentifier serialize(final InstanceIdentifier<?> input) {
-            List<YangInstanceIdentifier.PathArgument> domArgs = new LinkedList<>();
+            List<YangInstanceIdentifier.PathArgument> domArgs = new ArrayList<>();
             getCodecContextNode(input, domArgs);
             return YangInstanceIdentifier.create(domArgs);
         }
 
         @Override
         public InstanceIdentifier<?> deserialize(final YangInstanceIdentifier input) {
-            List<InstanceIdentifier.PathArgument> builder = new LinkedList<>();
-            getCodecContextNode(input, builder);
-            return InstanceIdentifier.create(builder);
+            final List<InstanceIdentifier.PathArgument> builder = new ArrayList<>();
+            final NodeCodecContext codec = getCodecContextNode(input, builder);
+            return codec == null ? null : InstanceIdentifier.create(builder);
         }
     }
 
@@ -342,26 +359,26 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
 
     private class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
 
-        private final ImmutableMap<QName, ValueContext> keyValueContexts;
-        private final QName name;
+        private final ImmutableSortedMap<QName, ValueContext> keyValueContexts;
+        private final ListSchemaNode schema;
         private final Constructor<? extends Identifier<?>> constructor;
         private final Class<?> identifiable;
 
-        public IdentifiableItemCodec(final QName name, final Class<? extends Identifier<?>> keyClass,
+        public IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
                 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
-            this.name = name;
+            this.schema = schema;
             this.identifiable = identifiable;
-            this.keyValueContexts = ImmutableMap.copyOf(keyValueContexts);
+            this.keyValueContexts = ImmutableSortedMap.copyOf(keyValueContexts);
             this.constructor = getConstructor(keyClass);
         }
 
         @Override
         public IdentifiableItem<?, ?> deserialize(final NodeIdentifierWithPredicates input) {
             ArrayList<Object> bindingValues = new ArrayList<>();
-            for (Entry<QName, Object> yangEntry : input.getKeyValues().entrySet()) {
-                QName yangName = yangEntry.getKey();
-                Object yangValue = yangEntry.getValue();
-                bindingValues.add(keyValueContexts.get(yangName).deserialize(yangValue));
+
+            for(QName key: schema.getKeyDefinition()) {
+                Object yangValue = input.getKeyValues().get(key);
+                bindingValues.add(keyValueContexts.get(key).deserialize(yangValue));
             }
             try {
                 final Identifier<?> identifier = constructor.newInstance(bindingValues.toArray());
@@ -381,7 +398,7 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
             for (Entry<QName, ValueContext> valueCtx : keyValueContexts.entrySet()) {
                 values.put(valueCtx.getKey(), valueCtx.getValue().getAndSerialize(value));
             }
-            return new NodeIdentifierWithPredicates(name, values);
+            return new NodeIdentifierWithPredicates(schema.getQName(), values);
         }
 
     }
@@ -408,7 +425,7 @@ class BindingCodecContext implements CodecContextFactory, Immutable {
             QName name = leaf.getDomPathArgument().getNodeType();
             valueCtx.put(name, new ValueContext(identifier, leaf));
         }
-        return new IdentifiableItemCodec(schema.getQName(), identifier, listClz, valueCtx);
+        return new IdentifiableItemCodec(schema, identifier, listClz, valueCtx);
     }
 
 }