Binding2 runtime - Codecs impl #2 53/58253/16
authorMartin Ciglan <martin.ciglan@pantheon.tech>
Mon, 5 Jun 2017 14:27:47 +0000 (16:27 +0200)
committerMartin Ciglan <martin.ciglan@pantheon.tech>
Mon, 12 Jun 2017 13:38:22 +0000 (13:38 +0000)
- NodeCodecContext & relatives

TODO: test coverage

Change-Id: I024618f2b80207e47eec85819390868ee34b6407
Signed-off-by: Martin Ciglan <martin.ciglan@pantheon.tech>
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java [new file with mode: 0644]
binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java [new file with mode: 0644]

diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java
new file mode 100644 (file)
index 0000000..33e55c6
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.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);
+    }
+
+    protected MissingSchemaException(final String msg, final Throwable cause) {
+        super(msg, cause);
+    }
+
+    private static MissingSchemaException create(final String format, final Object... args) {
+        return new MissingSchemaException(String.format(format, args));
+    }
+
+    public 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());
+        }
+    }
+
+    public 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/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java
new file mode 100644 (file)
index 0000000..333ef2a
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableCollection;
+import java.io.IOException;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingNormalizedNodeCachingCodec;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.MissingSchemaException;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializer;
+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.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
+@Beta
+abstract class DataContainerCodecContext<D extends TreeNode, T> extends NodeCodecContext<D> {
+
+    private final DataContainerCodecPrototype<T> prototype;
+    private volatile TreeNodeSerializer eventStreamSerializer;
+
+    protected DataContainerCodecContext(final DataContainerCodecPrototype<T> prototype) {
+        this.prototype = prototype;
+    }
+
+    @Nonnull
+    @Override
+    public final T getSchema() {
+        return prototype.getSchema();
+    }
+
+    protected final QNameModule namespace() {
+        return prototype.getNamespace();
+    }
+
+    protected final CodecContextFactory factory() {
+        return prototype.getFactory();
+    }
+
+    @Override
+    protected YangInstanceIdentifier.PathArgument getDomPathArgument() {
+        return prototype.getYangArg();
+    }
+
+    /**
+     * Returns nested node context using supplied YANG Instance Identifier
+     *
+     * @param arg Yang Instance Identifier Argument
+     * @return Context of child
+     * @throws IllegalArgumentException If supplied argument does not represent valid child.
+     */
+    @Nonnull
+    @Override
+    public abstract NodeCodecContext<?> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg);
+
+    /**
+     * Returns nested node context using supplied Binding Instance Identifier
+     * and adds YANG instance identifiers to supplied list.
+     *
+     * @param arg Binding Instance Identifier Argument
+     * @return Context of child or null if supplied {@code arg} does not represent valid child.
+     * @throws IllegalArgumentException If supplied argument does not represent valid child.
+     */
+    @SuppressWarnings("unchecked")
+    @Nonnull
+    @Override
+    public DataContainerCodecContext<?,?> bindingPathArgumentChild(@Nonnull final TreeArgument<?> arg,
+            final List<PathArgument> builder) {
+        final DataContainerCodecContext<?,?> child = streamChild((Class<? extends TreeNode>) arg.getType());
+        if (builder != null) {
+            child.addYangPathArgument(arg,builder);
+        }
+        return child;
+    }
+
+    /**
+     * Returns de-serialized Binding Path Argument from YANG instance identifier.
+     *
+     * @param domArg input path argument
+     * @return returns binding path argument
+     */
+    @SuppressWarnings("rawtypes")
+    protected TreeArgument getBindingPathArgument(final YangInstanceIdentifier.PathArgument domArg) {
+        return bindingArg();
+    }
+
+    @SuppressWarnings("rawtypes")
+    protected final TreeArgument bindingArg() {
+        return prototype.getBindingArg();
+    }
+
+    @Nonnull
+    @SuppressWarnings("unchecked")
+    @Override
+    public final Class<D> getBindingClass() {
+        return Class.class.cast(prototype.getBindingClass());
+    }
+
+    /**
+     * Returns child context as if it was walked by
+     * {@link BindingStreamEventWriter}. This means that to enter case, one
+     * must issue getChild(ChoiceClass).getChild(CaseClass).
+     *
+     * @param childClass input child class
+     * @return Context of child node or null, if supplied class is not subtree child
+     * @throws IllegalArgumentException If supplied child class is not valid in specified context.
+     */
+    @Nonnull
+    @Override
+    public abstract <DV extends TreeNode> DataContainerCodecContext<DV,?> streamChild(@Nonnull final Class<DV> childClass) throws IllegalArgumentException;
+
+    /**
+     * Returns child context as if it was walked by
+     * {@link BindingStreamEventWriter}. This means that to enter case, one
+     * must issue getChild(ChoiceClass).getChild(CaseClass).
+     *
+     * @param childClass input child class
+     * @return Context of child or Optional absent is supplied class is not applicable in context.
+     */
+    @Override
+    public abstract <DV extends TreeNode> Optional<DataContainerCodecContext<DV, ?>> possibleStreamChild(@Nonnull
+        final Class<DV> childClass);
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" + prototype.getBindingClass() + "]";
+    }
+
+    @Nonnull
+    @Override
+    public BindingNormalizedNodeCachingCodec<D> createCachingCodec(
+            @Nonnull final ImmutableCollection<Class<? extends TreeNode>> cacheSpecifier) {
+
+        //TODO: implement in cache-related patches to come
+        throw new NotImplementedException();
+    }
+
+    BindingStreamEventWriter createWriter(final NormalizedNodeStreamWriter domWriter) {
+
+        //TODO: streamWriter to come
+        throw new NotImplementedException();
+    }
+
+    @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);
+        MissingClassInLoadingStrategyException.check(factory().getRuntimeContext().getStrategy(), childClass);
+        throw IncorrectNestingException.create(message, args);
+    }
+
+    TreeNodeSerializer eventStreamSerializer() {
+        if(eventStreamSerializer == null) {
+            eventStreamSerializer = factory().getEventStreamSerializer(getBindingClass());
+        }
+        return eventStreamSerializer;
+    }
+
+    @Nonnull
+    @Override
+    public NormalizedNode<?, ?> serialize(@Nonnull final D data) {
+        final NormalizedNodeResult result = new NormalizedNodeResult();
+        // We create DOM stream writer which produces normalized nodes
+        final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+        writeAsNormalizedNode(data, domWriter);
+        return result.getResult();
+    }
+
+    @Override
+    public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) {
+        try {
+            eventStreamSerializer().serialize(data, createWriter(writer));
+        } catch (final IOException e) {
+            throw new IllegalStateException("Failed to serialize Binding DTO",e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java
new file mode 100644 (file)
index 0000000..c4a4f60
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.NodeCodecContext.CodecContextFactory;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Item;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeRoot;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
+@Beta
+final class DataContainerCodecPrototype<T> implements NodeContextSupplier {
+
+    private final T schema;
+    private final QNameModule namespace;
+    private final CodecContextFactory factory;
+    private final Class<?> bindingClass;
+    private final Item<?> bindingArg;
+    private final YangInstanceIdentifier.PathArgument yangArg;
+    private volatile DataContainerCodecContext<?,T> instance = null;
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    private DataContainerCodecPrototype(final Class<?> cls, final YangInstanceIdentifier.PathArgument arg, final T nodeSchema,
+            final CodecContextFactory factory) {
+        this.bindingClass = Preconditions.checkNotNull(cls);
+        this.yangArg = Preconditions.checkNotNull(arg);
+        this.schema = Preconditions.checkNotNull(nodeSchema);
+        this.factory = Preconditions.checkNotNull(factory);
+
+        this.bindingArg = new Item(bindingClass);
+
+        if (arg instanceof AugmentationIdentifier) {
+            this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null).getModule();
+        } else {
+            this.namespace = arg.getNodeType().getModule();
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Class<?> cls, final T schema,
+            final CodecContextFactory factory) {
+        return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
+    }
+
+    static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
+        final SchemaContext schema = factory.getRuntimeContext().getSchemaContext();
+        final NodeIdentifier arg = NodeIdentifier.create(schema.getQName());
+        return new DataContainerCodecPrototype<>(TreeRoot.class, arg, schema, factory);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
+                                               final AugmentationSchema schema, final CodecContextFactory factory) {
+        return new DataContainerCodecPrototype(augClass, arg, schema, factory);
+    }
+
+    static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass, final NotificationDefinition schema, final CodecContextFactory factory) {
+        final PathArgument arg = NodeIdentifier.create(schema.getQName());
+        return new DataContainerCodecPrototype<>(augClass, arg, schema, factory);
+    }
+
+    protected T getSchema() {
+        return schema;
+    }
+
+    protected QNameModule getNamespace() {
+        return namespace;
+    }
+
+    protected CodecContextFactory getFactory() {
+        return factory;
+    }
+
+    protected Class<?> getBindingClass() {
+        return bindingClass;
+    }
+
+    protected Item<?> getBindingArg() {
+        return bindingArg;
+    }
+
+    protected YangInstanceIdentifier.PathArgument getYangArg() {
+        return yangArg;
+    }
+
+    @Nonnull
+    @Override
+    public DataContainerCodecContext<?,T> get() {
+        DataContainerCodecContext<?,T> tmp = instance;
+        if (tmp == null) {
+            synchronized (this) {
+                tmp = instance;
+                if (tmp == null) {
+                    tmp = createInstance();
+                    instance = tmp;
+                }
+            }
+        }
+
+        return tmp;
+    }
+
+    @GuardedBy("this")
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    protected DataContainerCodecContext<?, T> createInstance() {
+        //TODO - implement it
+        throw new NotImplementedException();
+    }
+
+    boolean isChoice() {
+        return schema instanceof ChoiceSchemaNode;
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java
new file mode 100644 (file)
index 0000000..16a4385
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Thrown where incorrect nesting of data structures was detected
+ * and was caused by user.
+ */
+@Beta
+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);
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java
new file mode 100644 (file)
index 0000000..288622e
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.api.AugmentationReader;
+import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Beta
+class LazyTreeNode<D extends TreeNode> implements InvocationHandler, AugmentationReader {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LazyTreeNode.class);
+    private static final String GET_IMPLEMENTED_INTERFACE = "implementedInterface";
+    private static final String TO_STRING = "toString";
+    private static final String EQUALS = "equals";
+    private static final String GET_AUGMENTATION = "getAugmentation";
+    private static final String HASHCODE = "hashCode";
+    private static final String AUGMENTATIONS = "augmentations";
+    private static final Object NULL_VALUE = new Object();
+
+    private final ConcurrentHashMap<Method, Object> cachedData = new ConcurrentHashMap<>();
+    private final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data;
+    private final TreeNodeCodecContext<D,?> context;
+
+    private volatile ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> cachedAugmentations = null;
+    private volatile Integer cachedHashcode = null;
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    LazyTreeNode(final TreeNodeCodecContext<D,?> ctx, final NormalizedNodeContainer data) {
+        this.context = Preconditions.checkNotNull(ctx, "Context must not be null");
+        this.data = Preconditions.checkNotNull(data, "Data must not be null");
+    }
+
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+        if (method.getParameterTypes().length == 0) {
+            final String name = method.getName();
+            if (GET_IMPLEMENTED_INTERFACE.equals(name)) {
+                return context.getBindingClass();
+            } else if (TO_STRING.equals(name)) {
+                return bindingToString();
+            } else if (HASHCODE.equals(name)) {
+                return bindingHashCode();
+            } else if (AUGMENTATIONS.equals(name)) {
+                return getAugmentationsImpl();
+            }
+            return getBindingData(method);
+        } else if (GET_AUGMENTATION.equals(method.getName())) {
+            return getAugmentationImpl((Class<?>) args[0]);
+        } else if (EQUALS.equals(method.getName())) {
+            return bindingEquals(args[0]);
+        }
+        throw new UnsupportedOperationException("Unsupported method " + method);
+    }
+
+    private boolean bindingEquals(final Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!context.getBindingClass().isAssignableFrom(other.getClass())) {
+            return false;
+        }
+        try {
+            for (final Method m : context.getHashCodeAndEqualsMethods()) {
+                final Object thisValue = getBindingData(m);
+                final Object otherValue = m.invoke(other);
+                /*
+                 *   added for valid byte array comparison, when list key type is binary
+                 *   deepEquals is not used since it does excessive amount of instanceof calls.
+                 */
+                if (thisValue instanceof byte[] && otherValue instanceof byte[]) {
+                    if (!Arrays.equals((byte[]) thisValue, (byte[]) otherValue)) {
+                        return false;
+                    }
+                } else if (!Objects.equals(thisValue, otherValue)){
+                    return false;
+                }
+            }
+
+            if (Augmentable.class.isAssignableFrom(context.getBindingClass())) {
+                if (!getAugmentationsImpl().equals(getAllAugmentations(other))) {
+                    return false;
+                }
+            }
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+            LOG.warn("Can not determine equality of {} and {}", this, other, e);
+            return false;
+        }
+        return true;
+    }
+
+    private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentations(final Object dataObject) {
+        if (dataObject instanceof AugmentationReader) {
+            return ((AugmentationReader) dataObject).getAugmentations(dataObject);
+        } else if (dataObject instanceof Augmentable<?>){
+            return BindingReflections.getAugmentations((Augmentable<?>) dataObject);
+        }
+
+        throw new IllegalArgumentException("Unable to get all augmentations from " + dataObject);
+    }
+
+    private Integer bindingHashCode() {
+        final Integer ret = cachedHashcode;
+        if (ret != null) {
+            return ret;
+        }
+
+        final int prime = 31;
+        int result = 1;
+        for (final Method m : context.getHashCodeAndEqualsMethods()) {
+            final Object value = getBindingData(m);
+            result = prime * result + Objects.hashCode(value);
+        }
+        if (Augmentable.class.isAssignableFrom(context.getBindingClass())) {
+            result = prime * result + (getAugmentationsImpl().hashCode());
+        }
+        cachedHashcode = result;
+        return result;
+    }
+
+    private Object getBindingData(final Method method) {
+        Object cached = cachedData.get(method);
+        if (cached == null) {
+            final Object readedValue = context.getBindingChildValue(method, data);
+            if (readedValue == null) {
+                cached = NULL_VALUE;
+            } else {
+                cached = readedValue;
+            }
+            cachedData.putIfAbsent(method, cached);
+        }
+
+        return cached == NULL_VALUE ? null : cached;
+    }
+
+    private Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentationsImpl() {
+        ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> ret = cachedAugmentations;
+        if (ret == null) {
+            synchronized (this) {
+                ret = cachedAugmentations;
+                if (ret == null) {
+                    ret = ImmutableMap.copyOf(context.getAllAugmentationsFrom(data));
+                    cachedAugmentations = ret;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    @Override
+    public Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object obj) {
+        Preconditions.checkArgument(this == Proxy.getInvocationHandler(obj),
+                "Supplied object is not associated with this proxy handler");
+
+        return getAugmentationsImpl();
+    }
+
+    private Object getAugmentationImpl(final Class<?> cls) {
+        final ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> aug = cachedAugmentations;
+        if (aug != null) {
+            return aug.get(cls);
+        }
+        Preconditions.checkNotNull(cls,"Supplied augmentation must not be null.");
+
+        @SuppressWarnings({"unchecked","rawtypes"})
+        final Optional<DataContainerCodecContext<?,?>> augCtx= context.possibleStreamChild((Class) cls);
+        if(augCtx.isPresent()) {
+            final Optional<NormalizedNode<?, ?>> augData = data.getChild(augCtx.get().getDomPathArgument());
+            if (augData.isPresent()) {
+                return augCtx.get().deserialize(augData.get());
+            }
+        }
+        return null;
+    }
+
+    public String bindingToString() {
+        final ToStringHelper helper = MoreObjects.toStringHelper(context.getBindingClass()).omitNullValues();
+
+        for (final Method m :context.getHashCodeAndEqualsMethods()) {
+            helper.add(m.getName(), getBindingData(m));
+        }
+        if (Augmentable.class.isAssignableFrom(context.getBindingClass())) {
+            helper.add("augmentations", getAugmentationsImpl());
+        }
+        return helper.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.context, this.data);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final LazyTreeNode<?> other = (LazyTreeNode<?>) obj;
+        return Objects.equals(context, other.context) && Objects.equals(data, other.data);
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java
new file mode 100644 (file)
index 0000000..f6835b3
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingNormalizedNodeCachingCodec;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingTreeNodeCodec;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+
+@Beta
+final class LeafNodeCodecContext<D extends TreeNode> extends NodeCodecContext<D> implements NodeContextSupplier {
+
+    private final YangInstanceIdentifier.PathArgument yangIdentifier;
+    private final Codec<Object, Object> valueCodec;
+    private final Method getter;
+    private final DataSchemaNode schema;
+    private final Object defaultObject;
+
+    LeafNodeCodecContext(final DataSchemaNode schema, final Codec<Object, Object> codec, final Method getter,
+            final SchemaContext schemaContext) {
+        this.yangIdentifier = new YangInstanceIdentifier.NodeIdentifier(schema.getQName());
+        this.valueCodec = Preconditions.checkNotNull(codec);
+        this.getter = Preconditions.checkNotNull(getter);
+        this.schema = Preconditions.checkNotNull(schema);
+
+        this.defaultObject = createDefaultObject(schema, valueCodec, schemaContext);
+    }
+
+    private static Object createDefaultObject(final DataSchemaNode schema, final Codec<Object, Object> codec,
+            final SchemaContext schemaContext) {
+        if (schema instanceof LeafSchemaNode) {
+            Object defaultValue = ((LeafSchemaNode) schema).getDefault();
+            TypeDefinition<?> type = ((LeafSchemaNode) schema).getType();
+            if (defaultValue != null) {
+                if (type instanceof IdentityrefTypeDefinition) {
+                    return qnameDomValueFromString(codec, schema, (String) defaultValue, schemaContext);
+                }
+                return domValueFromString(codec, type, defaultValue);
+            }
+            else {
+                while (type.getBaseType() != null && type.getDefaultValue() == null) {
+                    type = type.getBaseType();
+                }
+
+                defaultValue = type.getDefaultValue();
+                if (defaultValue != null) {
+                    if (type instanceof IdentityrefTypeDefinition) {
+                        return qnameDomValueFromString(codec, schema, (String) defaultValue, schemaContext);
+                    }
+                    return domValueFromString(codec, type, defaultValue);
+                }
+            }
+        }
+        return null;
+    }
+
+    private static Object qnameDomValueFromString(final Codec<Object, Object> codec, final DataSchemaNode schema,
+            final String defaultValue, final SchemaContext schemaContext) {
+        final int prefixEndIndex = defaultValue.indexOf(':');
+        if (prefixEndIndex != -1) {
+            final String defaultValuePrefix = defaultValue.substring(0, prefixEndIndex);
+
+            final Module module = schemaContext.findModuleByNamespaceAndRevision(schema.getQName().getNamespace(),
+                    schema.getQName().getRevision());
+
+            if (module.getPrefix().equals(defaultValuePrefix)) {
+                return codec
+                        .deserialize(QName.create(module.getQNameModule(), defaultValue.substring(prefixEndIndex + 1)));
+            } else {
+                final Set<ModuleImport> imports = module.getImports();
+                for (final ModuleImport moduleImport : imports) {
+                    if (moduleImport.getPrefix().equals(defaultValuePrefix)) {
+                        final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(),
+                                moduleImport.getRevision());
+                        return codec.deserialize(QName.create(importedModule.getQNameModule(),
+                                defaultValue.substring(prefixEndIndex + 1)));
+                    }
+                }
+                return null;
+            }
+        }
+
+        return codec.deserialize(QName.create(schema.getQName(), defaultValue));
+    }
+
+    private static Object domValueFromString(final Codec<Object, Object> codec, final TypeDefinition<?> type,
+            final Object defaultValue) {
+        final TypeDefinitionAwareCodec<?, ?> typeDefAwareCodec = TypeDefinitionAwareCodec.from(type);
+        if (typeDefAwareCodec != null) {
+            final Object castedDefaultValue = typeDefAwareCodec.deserialize((String) defaultValue);
+            return codec.deserialize(castedDefaultValue);
+        }
+        // FIXME: BUG-4647 Refactor / redesign this to throw hard error,
+        // once BUG-4638 is fixed and will provide proper getDefaultValue implementation.
+        return null;
+    }
+
+    @Override
+    protected YangInstanceIdentifier.PathArgument getDomPathArgument() {
+        return yangIdentifier;
+    }
+
+    protected Codec<Object, Object> getValueCodec() {
+        return valueCodec;
+    }
+
+    @Nonnull
+    @Override
+    public D deserialize(@Nonnull final NormalizedNode<?, ?> normalizedNode) {
+        throw new UnsupportedOperationException("Leaf can not be deserialized to TreeNode");
+    }
+
+    @Nonnull
+    @Override
+    public NodeCodecContext<?> get() {
+        return this;
+    }
+
+    final Method getGetter() {
+        return getter;
+    }
+
+    @Nonnull
+    @Override
+    public BindingTreeNodeCodec<?> bindingPathArgumentChild(@Nonnull final TreeArgument<?> arg,
+            final List<YangInstanceIdentifier.PathArgument> builder) {
+        throw new IllegalArgumentException("Leaf does not have children");
+    }
+
+    @Nonnull
+    @Override
+    public BindingNormalizedNodeCachingCodec<D> createCachingCodec(
+            @Nonnull final ImmutableCollection<Class<? extends TreeNode>> cacheSpecifier) {
+        throw new UnsupportedOperationException("Leaves does not support caching codec.");
+    }
+
+    @Nonnull
+    @Override
+    public Class<D> getBindingClass() {
+        throw new UnsupportedOperationException("Leaf does not have DataObject representation");
+    }
+
+    @Nonnull
+    @Override
+    public NormalizedNode<?, ?> serialize(@Nonnull final D data) {
+        throw new UnsupportedOperationException("Separate serialization of leaf node is not supported.");
+    }
+
+    @Override
+    public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) {
+        throw new UnsupportedOperationException("Separate serialization of leaf node is not supported.");
+    }
+
+    @Nonnull
+    @Override
+    public <E extends TreeNode> BindingTreeNodeCodec<E> streamChild(@Nonnull final Class<E> childClass) {
+        throw new IllegalArgumentException("Leaf does not have children");
+    }
+
+    @Override
+    public <E extends TreeNode> Optional<? extends BindingTreeNodeCodec<E>> possibleStreamChild(
+            @Nonnull final Class<E> childClass) {
+        throw new IllegalArgumentException("Leaf does not have children");
+    }
+
+    @Nonnull
+    @Override
+    public BindingTreeNodeCodec<?> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument child) {
+        throw new IllegalArgumentException("Leaf does not have children");
+    }
+
+    @Override
+    protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
+        if (normalizedNode instanceof LeafNode<?>) {
+            return valueCodec.deserialize(normalizedNode.getValue());
+        }
+        if (normalizedNode instanceof LeafSetNode<?>) {
+            @SuppressWarnings("unchecked")
+            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;
+        }
+        return null;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public TreeArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
+        Preconditions.checkArgument(getDomPathArgument().equals(arg));
+        return null;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public YangInstanceIdentifier.PathArgument serializePathArgument(final TreeArgument arg) {
+        return getDomPathArgument();
+    }
+
+    @Nonnull
+    @Override
+    public DataSchemaNode getSchema() {
+        return schema;
+    }
+
+    /**
+     * Return the default value object.
+     *
+     * @return The default value object, or null if the default value is not defined.
+     */
+    @Nullable
+    Object defaultObject() {
+        return defaultObject;
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java
new file mode 100644 (file)
index 0000000..6296b99
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.MissingSchemaException;
+import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy;
+
+/**
+ * Thrown when user schema for supplied binding class is available in present schema context, but
+ * binding class itself is not known to codecs because backing class loading strategy did not
+ * provided it.
+ */
+@Beta
+public class MissingClassInLoadingStrategyException extends MissingSchemaException {
+
+    private static final long serialVersionUID = 1L;
+
+    protected MissingClassInLoadingStrategyException(final String msg, final Throwable cause) {
+        super(msg, cause);
+    }
+
+    public static void check(final ClassLoadingStrategy strategy, final Class<?> childClass) {
+        try {
+            strategy.loadClass(childClass.getName());
+        } catch (final ClassNotFoundException e) {
+            final String message =
+                    String.format("User supplied class %s is not available in %s.", childClass.getName(), strategy);
+            throw new MissingClassInLoadingStrategyException(message, e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java
new file mode 100644 (file)
index 0000000..e8131d2
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.MissingSchemaException;
+import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.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.
+ */
+@Beta
+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);
+        }
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java
new file mode 100644 (file)
index 0000000..196eb86
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingTreeNodeCodec;
+import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
+import org.opendaylight.mdsal.binding.javav2.spec.base.IdentifiableItem;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializer;
+import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+/**
+ *
+ * Location specific context for schema nodes, which contains codec specific
+ * information to properly serialize / deserialize from Java YANG Binding data
+ * to NormalizedNode data.
+ *
+ * Two core subtypes of codec context are available:
+ * <ul>
+ * <li>{@link LeafNodeCodecContext} - Context for nodes, which does not contain
+ * any nested YANG modeled substructures.</li>
+ * <li>{@link TreeNodeCodecContext} - Context for nodes, which does contain
+ * nested YANG modeled substructures. This context nodes contains context
+ * for children nodes.</li>
+ * </ul>
+ *
+ */
+@Beta
+abstract class NodeCodecContext<D extends TreeNode> implements BindingTreeNodeCodec<D>{
+    /**
+     * Returns Yang Instance Identifier Path Argument of current node
+     *
+     * @return DOM Path Argument of node
+     */
+    protected abstract YangInstanceIdentifier.PathArgument getDomPathArgument();
+
+    /**
+     *
+     * Immutable factory, which provides access to runtime context,
+     * create leaf nodes and provides path argument codecs.
+     * <p>
+     * During lifetime of factory all calls for same arguments to method must return
+     * equal result (not necessary same instance of result).
+     *
+     */
+    protected interface CodecContextFactory {
+        /**
+         * Returns immutable runtime context associated with this factory.
+         * @return runtime context
+         */
+        BindingRuntimeContext getRuntimeContext();
+
+        /**
+         * Returns leaf nodes for supplied data container and parent class.
+         *
+         * @param type Binding type for which leaves should be loaded.
+         * @param schema  Instantiated schema of binding type.
+         * @return Map of local name to leaf node context.
+         */
+        ImmutableMap<String, LeafNodeCodecContext<?>> getLeafNodes(Class<?> type, DataNodeContainer schema);
+
+        /**
+         * Returns Path argument codec for list item
+         *
+         * @param type Type of list item
+         * @param schema Schema of list item
+         * @return Path argument codec for supplied list item.
+         */
+        Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> getPathArgumentCodec(Class<?> type,
+           ListSchemaNode schema);
+
+        TreeNodeSerializer getEventStreamSerializer(Class<?> type);
+    }
+
+    /**
+     *
+     * Serializes supplied Binding Path Argument
+     * and adds all necessary YANG instance identifiers to supplied list.
+     *
+     * @param arg Binding Path Argument
+     * @param builder DOM Path argument.
+     */
+    @SuppressWarnings("rawtypes")
+    protected void addYangPathArgument(final TreeArgument arg,
+            final List<YangInstanceIdentifier.PathArgument> builder) {
+        if (builder != null) {
+            builder.add(getDomPathArgument());
+        }
+    }
+
+    protected abstract Object deserializeObject(NormalizedNode<?, ?> normalizedNode);
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java
new file mode 100644 (file)
index 0000000..b7ae443
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Supplier;
+import javax.annotation.Nonnull;
+
+/**
+ * Type capture of an entity producing NodeCodecContexts.
+ */
+@Beta
+interface NodeContextSupplier extends Supplier<NodeCodecContext<?>> {
+
+    @Override
+    @Nonnull
+    NodeCodecContext<?> get();
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java
new file mode 100644 (file)
index 0000000..94c2810
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base;
+
+import com.google.common.annotations.Beta;
+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.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;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy;
+import org.opendaylight.mdsal.binding.javav2.model.api.Type;
+import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+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.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Beta
+abstract class TreeNodeCodecContext<D extends TreeNode, T extends DataNodeContainer>
+        extends DataContainerCodecContext<D, T> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TreeNodeCodecContext.class);
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class);
+    private static final MethodType TREENODE_TYPE = MethodType.methodType(TreeNode.class, InvocationHandler.class);
+    private static final Comparator<Method> METHOD_BY_ALPHABET = Comparator.comparing(Method::getName);
+
+    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;
+    private final ImmutableMap<AugmentationIdentifier, Type> possibleAugmentations;
+    private final MethodHandle proxyConstructor;
+
+    private final ConcurrentMap<PathArgument, DataContainerCodecPrototype<?>> byYangAugmented =
+            new ConcurrentHashMap<>();
+    private final ConcurrentMap<Class<?>, DataContainerCodecPrototype<?>> byStreamAugmented = new ConcurrentHashMap<>();
+
+
+    protected TreeNodeCodecContext(final DataContainerCodecPrototype<T> prototype) {
+        super(prototype);
+
+        this.leafChild = factory().getLeafNodes(getBindingClass(), getSchema());
+
+        final Map<Class<?>, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(getBindingClass());
+
+        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 (final LeafNodeCodecContext<?> leaf : leafChild.values()) {
+            byMethodBuilder.put(leaf.getGetter(), leaf);
+            byYangBuilder.put(leaf.getDomPathArgument(), leaf);
+        }
+
+        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);
+            //TODO: get cases in consideration - finish in patches to come
+            //if (childProto.isChoice()) {
+        }
+        this.byMethod = ImmutableSortedMap.copyOfSorted(byMethodBuilder);
+        this.byYang = ImmutableMap.copyOf(byYangBuilder);
+        this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
+        byBindingArgClassBuilder.putAll(byStreamClass);
+        this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
+
+
+        if (Augmentable.class.isAssignableFrom(getBindingClass())) {
+            this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
+        } else {
+            this.possibleAugmentations = ImmutableMap.of();
+        }
+        reloadAllAugmentations();
+
+        final Class<?> proxyClass = Proxy.getProxyClass(getBindingClass().getClassLoader(),  new Class[] { getBindingClass(), AugmentationHolder.class });
+        try {
+            proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE)
+                    .asType(TREENODE_TYPE);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new IllegalStateException("Failed to find contructor for class " + proxyClass);
+        }
+    }
+
+    private void reloadAllAugmentations() {
+        for (final Entry<AugmentationIdentifier, Type> augment : possibleAugmentations.entrySet()) {
+            final DataContainerCodecPrototype<?> augProto = getAugmentationPrototype(augment.getValue());
+            if (augProto != null) {
+                byYangAugmented.putIfAbsent(augProto.getYangArg(), augProto);
+                byStreamAugmented.putIfAbsent(augProto.getBindingClass(), augProto);
+            }
+        }
+    }
+
+    @Nonnull
+    @SuppressWarnings("unchecked")
+    @Override
+    public <DV extends TreeNode> DataContainerCodecContext<DV, ?> streamChild(@Nonnull final Class<DV> childClass) {
+        final DataContainerCodecPrototype<?> childProto = streamChildPrototype(childClass);
+        return (DataContainerCodecContext<DV, ?>) childNonNull(childProto, childClass, " Child %s is not valid child.",
+                childClass).get();
+    }
+
+    private final DataContainerCodecPrototype<?> streamChildPrototype(final Class<?> childClass) {
+        final DataContainerCodecPrototype<?> childProto = byStreamClass.get(childClass);
+        if (childProto != null) {
+            return childProto;
+        }
+        if (Augmentation.class.isAssignableFrom(childClass)) {
+            return augmentationByClass(childClass);
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <DV extends TreeNode> Optional<DataContainerCodecContext<DV, ?>> possibleStreamChild(
+            @Nonnull final Class<DV> childClass) {
+        final DataContainerCodecPrototype<?> childProto = streamChildPrototype(childClass);
+        if (childProto != null) {
+            return Optional.of((DataContainerCodecContext<DV,?>) childProto.get());
+        }
+        return Optional.absent();
+    }
+
+    @Nonnull
+    @Override
+    public DataContainerCodecContext<?,?> bindingPathArgumentChild(final TreeArgument<?> arg,
+            final List<PathArgument> builder) {
+
+        final Class<? extends TreeNode> argType = (Class<? extends TreeNode>) arg.getType();
+        DataContainerCodecPrototype<?> ctxProto = byBindingArgClass.get(argType);
+        if (ctxProto == null && Augmentation.class.isAssignableFrom(argType)) {
+            ctxProto = augmentationByClass(argType);
+        }
+        final DataContainerCodecContext<?, ?> context =
+                childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get();
+        //TODO: get cases in consideration - finish in patches to come
+//        if (context instanceof ChoiceNodeCodecContext) {
+        context.addYangPathArgument(arg, builder);
+        return context;
+    }
+
+    @Nonnull
+    @SuppressWarnings("unchecked")
+    @Override
+    public NodeCodecContext<D> yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) {
+        final NodeContextSupplier childSupplier;
+        if (arg instanceof NodeIdentifierWithPredicates) {
+            childSupplier = byYang.get(new NodeIdentifier(arg.getNodeType()));
+        } else if (arg instanceof AugmentationIdentifier) {
+            childSupplier = yangAugmentationChild((AugmentationIdentifier) arg);
+        } else {
+            childSupplier = byYang.get(arg);
+        }
+
+        return (NodeCodecContext<D>) childNonNull(childSupplier, arg,
+                "Argument %s is not valid child of %s", arg, getSchema()).get();
+    }
+
+    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) {
+        final DataSchemaNode origDef = factory().getRuntimeContext().getSchemaDefinition(childClass);
+        // Direct instantiation or use in same module in which grouping
+        // was defined.
+        DataSchemaNode sameName;
+        try {
+            sameName = getSchema().getDataChildByName(origDef.getQName());
+        } catch (final IllegalArgumentException e) {
+            sameName = null;
+        }
+        final DataSchemaNode childSchema;
+        if (sameName != null) {
+            // Exactly same schema node
+            if (origDef.equals(sameName)) {
+                childSchema = sameName;
+                // We check if instantiated node was added via uses
+                // statement and is instantiation of same grouping
+            } else if (origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(sameName))) {
+                childSchema = sameName;
+            } else {
+                // Node has same name, but clearly is different
+                childSchema = null;
+            }
+        } else {
+            // We are looking for instantiation via uses in other module
+            final QName instantiedName = QName.create(namespace(), origDef.getQName().getLocalName());
+            final DataSchemaNode potential = getSchema().getDataChildByName(instantiedName);
+            // We check if it is really instantiated from same
+            // definition as class was derived
+            if (potential != null && origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential))) {
+                childSchema = potential;
+            } else {
+                childSchema = null;
+            }
+        }
+        final DataSchemaNode nonNullChild =
+                childNonNull(childSchema, childClass, "Node %s does not have child named %s", getSchema(), childClass);
+        return DataContainerCodecPrototype.from(childClass, nonNullChild, factory());
+    }
+
+    private DataContainerCodecPrototype<?> yangAugmentationChild(final AugmentationIdentifier arg) {
+        final DataContainerCodecPrototype<?> firstTry = byYangAugmented.get(arg);
+        if (firstTry != null) {
+            return firstTry;
+        }
+        if (possibleAugmentations.containsKey(arg)) {
+            reloadAllAugmentations();
+            return byYangAugmented.get(arg);
+        }
+        return null;
+    }
+
+    @Nullable
+    private DataContainerCodecPrototype<?> augmentationByClass(@Nonnull final Class<?> childClass) {
+        final DataContainerCodecPrototype<?> firstTry = augmentationByClassOrEquivalentClass(childClass);
+        if (firstTry != null) {
+            return firstTry;
+        }
+        reloadAllAugmentations();
+        return augmentationByClassOrEquivalentClass(childClass);
+    }
+
+    @Nullable
+    private final DataContainerCodecPrototype<?> augmentationByClassOrEquivalentClass(@Nonnull final Class<?> childClass) {
+        final DataContainerCodecPrototype<?> childProto = byStreamAugmented.get(childClass);
+        if (childProto != null) {
+            return childProto;
+        }
+
+        /*
+         * It is potentially mismatched valid augmentation - we look up equivalent augmentation
+         * using reflection and walk all stream child and compare augmenations classes if they are
+         * equivalent.
+         *
+         * FIXME: Cache mapping of mismatched augmentation to real one, to speed up lookup.
+         */
+        @SuppressWarnings("rawtypes")
+        final Class<?> augTarget = BindingReflections.findAugmentationTarget((Class) childClass);
+        if ((getBindingClass().equals(augTarget))) {
+            for (final DataContainerCodecPrototype<?> realChild : byStreamAugmented.values()) {
+                if (Augmentation.class.isAssignableFrom(realChild.getBindingClass())
+                        && BindingReflections.isSubstitutionFor(childClass, realChild.getBindingClass())) {
+                    return realChild;
+                }
+            }
+        }
+        return null;
+    }
+
+    private DataContainerCodecPrototype<?> getAugmentationPrototype(final Type value) {
+        final ClassLoadingStrategy loader = factory().getRuntimeContext().getStrategy();
+        @SuppressWarnings("rawtypes")
+        final Class augClass;
+        try {
+            augClass = loader.loadClass(value);
+        } catch (final ClassNotFoundException e) {
+            LOG.debug("Failed to load augmentation prototype for {}. Will be retried when needed.", value, e);
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        final Entry<AugmentationIdentifier, AugmentationSchema> augSchema = factory().getRuntimeContext()
+                .getResolvedAugmentationSchema(getSchema(), augClass);
+        return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory());
+    }
+
+    @SuppressWarnings("rawtypes")
+    Object getBindingChildValue(final Method method, final NormalizedNodeContainer domData) {
+        final NodeCodecContext<?> childContext = byMethod.get(method).get();
+        @SuppressWarnings("unchecked")
+        final Optional<NormalizedNode<?, ?>> domChild = domData.getChild(childContext.getDomPathArgument());
+        if (domChild.isPresent()) {
+            return childContext.deserializeObject(domChild.get());
+        } else if (childContext instanceof LeafNodeCodecContext) {
+            return ((LeafNodeCodecContext)childContext).defaultObject();
+        } else {
+            return null;
+        }
+    }
+
+    protected final D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
+        try {
+            return (D) proxyConstructor.invokeExact((InvocationHandler) new LazyTreeNode<>(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")
+        final Map map = new HashMap<>();
+
+        for (final NormalizedNode<?, ?> childValue : data.getValue()) {
+            if (childValue instanceof AugmentationNode) {
+                final AugmentationNode augDomNode = (AugmentationNode) childValue;
+                final DataContainerCodecPrototype<?> codecProto = yangAugmentationChild(augDomNode.getIdentifier());
+                if (codecProto != null) {
+                    final DataContainerCodecContext<?, ?> codec = codecProto.get();
+                    map.put(codec.getBindingClass(), codec.deserializeObject(augDomNode));
+                }
+            }
+        }
+        for (final DataContainerCodecPrototype<?> value : byStreamAugmented.values()) {
+            final Optional<NormalizedNode<?, ?>> augData = data.getChild(value.getYangArg());
+            if (augData.isPresent()) {
+                map.put(value.getBindingClass(), value.get().deserializeObject(augData.get()));
+            }
+        }
+        return map;
+    }
+
+    public Collection<Method> getHashCodeAndEqualsMethods() {
+        return byMethod.keySet();
+    }
+
+    @Override
+    public TreeArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
+        Preconditions.checkArgument(getDomPathArgument().equals(arg));
+        return bindingArg();
+    }
+
+    @Override
+    public YangInstanceIdentifier.PathArgument serializePathArgument(final TreeArgument arg) {
+        Preconditions.checkArgument(bindingArg().equals(arg));
+        return getDomPathArgument();
+    }
+
+}
\ No newline at end of file