Add codec support for anyxml classes 43/81643/19
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 14 Apr 2019 09:25:17 +0000 (11:25 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 18 Apr 2019 16:08:42 +0000 (18:08 +0200)
ForeignOpaqueData and ForeignOpaqueObject provide generalized
support for proxying over to yang.data.api.ForeignDataNode, hence
supporting anydata/anyxml in all possible object models.

The codec tree is extended with BindingOpaqueObjectCodecTreeNode,
which supports all OpaqueObjects, with the restriction that only
AnyXmlNodes (i.e. those with DOMSource object model) are supported
due to streaming restrictions in NormalizedNodeStreamWriter.

JIRA: MDSAL-285
Change-Id: I9192008ae169cf49a98f64a218a33d470d134eae
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
13 files changed:
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataNodeContainerSerializerSource.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataObjectSerializerSource.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingNormalizedNodeCodecRegistry.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafSetNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ValueNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java [new file with mode: 0644]

diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java
new file mode 100644 (file)
index 0000000..58818fb
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.api;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
+
+@Beta
+public interface BindingOpaqueObjectCodecTreeNode<T extends OpaqueObject<T>> extends BindingObjectCodecTreeNode<T>,
+        BindingNormalizedNodeCodec<T> {
+
+}
index 004cbadc3e0ebd39ddd2a19c890b2b1a1adccd1f..375fe00bd709cba2540843e09dc60c808f727799 100644 (file)
@@ -5,9 +5,9 @@
  * 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.dom.codec.gen.impl;
 
+import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
 import java.util.HashMap;
@@ -110,20 +110,8 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
             if (!schemaChild.isAugmenting()) {
                 final String getter = BindingSchemaMapping.getGetterMethodName(schemaChild);
                 final Type childType = getterToType.get(getter);
-                if (childType == null) {
-                    // FIXME AnyXml nodes are ignored, since their type cannot be found in generated bindnig
-                    // Bug-706 https://bugs.opendaylight.org/show_bug.cgi?id=706
-                    if (schemaChild instanceof AnyXmlSchemaNode) {
-                        LOG.warn("Node {} will be ignored. AnyXml is not yet supported from binding aware code."
-                                + "Binding Independent code can be used to serialize anyXml nodes.",
-                                schemaChild.getPath());
-                        continue;
-                    }
-
-                    throw new IllegalStateException(
-                        String.format("Unable to find type for child node %s. Expected child nodes: %s",
-                            schemaChild.getPath(), getterToType));
-                }
+                checkState(childType != null, "Unable to find type for child node %s. Expected child nodes: %s",
+                        schemaChild.getPath(), getterToType);
                 emitChild(sb, getter, childType, schemaChild);
             }
         }
index 79d47c42eb2de5171af75ee6b41c6bf54d8e5601..25ba79b376681fbab58e3b8dd873ae353f7b6e28 100644 (file)
@@ -66,7 +66,7 @@ abstract class DataObjectSerializerSource extends AbstractSource {
         return invoke(STREAM, "leafNode", escape(localName), value);
     }
 
-    protected static final CharSequence startLeafSet(final String localName,final CharSequence expected) {
+    protected static final CharSequence startLeafSet(final String localName, final CharSequence expected) {
         return invoke(STREAM, "startLeafSet", escape(localName), expected);
     }
 
@@ -110,17 +110,17 @@ abstract class DataObjectSerializerSource extends AbstractSource {
         return invoke(STREAM, "startAugmentationNode", key);
     }
 
-    protected static final CharSequence startChoiceNode(final CharSequence localName,final CharSequence expected) {
+    protected static final CharSequence startChoiceNode(final CharSequence localName, final CharSequence expected) {
         return invoke(STREAM, "startChoiceNode", localName, expected);
     }
 
-    protected static final CharSequence startCaseNode(final CharSequence localName,final CharSequence expected) {
+    protected static final CharSequence startCaseNode(final CharSequence localName, final CharSequence expected) {
         return invoke(STREAM, "startCase", localName, expected);
     }
 
-    protected static final CharSequence anyxmlNode(final String name, final String value)
+    protected static final CharSequence anyxmlNode(final String localName, final CharSequence value)
             throws IllegalArgumentException {
-        return invoke(STREAM, "anyxmlNode", escape(name), name);
+        return invoke(STREAM, "anyxmlNode", escape(localName), value);
     }
 
     protected static final CharSequence endNode() {
index 3ae940bc2b1c335010c464b9f614cddebda1c362..83cc618092afbb4c6803679a1bc037f2256f832e 100644 (file)
@@ -47,9 +47,11 @@ import org.opendaylight.yangtools.yang.binding.Identifiable;
 import org.opendaylight.yangtools.yang.binding.Identifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.Notification;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
@@ -237,8 +239,9 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
             final DataNodeContainer childSchema) {
         final Map<String, DataSchemaNode> getterToLeafSchema = new HashMap<>();
         for (final DataSchemaNode leaf : childSchema.getChildNodes()) {
-            if (leaf instanceof TypedDataSchemaNode) {
-                getterToLeafSchema.put(BindingSchemaMapping.getGetterMethodName((TypedDataSchemaNode) leaf), leaf);
+            // FIXME: support AnyDataSchemaNode, too
+            if (leaf instanceof TypedDataSchemaNode || leaf instanceof AnyXmlSchemaNode) {
+                getterToLeafSchema.put(BindingSchemaMapping.getGetterMethodName(leaf), leaf);
             }
         }
         return getLeafNodesUsingReflection(parentClass, getterToLeafSchema);
@@ -250,10 +253,6 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
         for (final Method method : parentClass.getMethods()) {
             if (method.getParameterCount() == 0) {
                 final DataSchemaNode schema = getterToLeafSchema.get(method.getName());
-                if (!(schema instanceof TypedDataSchemaNode)) {
-                    // We do not have schema for leaf, so we will ignore it (e.g. getClass).
-                    continue;
-                }
 
                 final ValueNodeCodecContext valueNode;
                 if (schema instanceof LeafSchemaNode) {
@@ -281,8 +280,15 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
                     final LeafListSchemaNode leafListSchema = (LeafListSchemaNode) schema;
                     final Codec<Object, Object> codec = getCodec(valueType, leafListSchema.getType());
                     valueNode = new LeafSetNodeCodecContext(leafListSchema, codec, method);
+                } else if (schema instanceof AnyXmlSchemaNode) {
+                    final Class<?> valueType = method.getReturnType();
+                    verify(OpaqueObject.class.isAssignableFrom(valueType), "Illegal value type %s", valueType);
+                    valueNode = new OpaqueNodeCodecContext.AnyXml<>((AnyXmlSchemaNode) schema, method,
+                            valueType.asSubclass(OpaqueObject.class));
                 } else {
-                    throw new IllegalStateException("Unhandled typed schema " + schema);
+                    verify(schema == null, "Unhandled schema %s for method %s", schema, method);
+                    // We do not have schema for leaf, so we will ignore it (e.g. getClass).
+                    continue;
                 }
 
                 leaves.put(schema.getQName().getLocalName(), valueNode);
index c16280679aa9c8cb3a0b93a1c579ad0eaa8bb1ee..3a9452922341d3a3ae832c9bedd921761c0d9cbd 100644 (file)
@@ -133,6 +133,7 @@ public class BindingNormalizedNodeCodecRegistry implements DataObjectSerializerR
     }
 
     @Override
+    @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
     public ContainerNode toNormalizedNodeRpcData(final DataContainer data) {
         // FIXME: Should the cast to DataObject be necessary?
         return serializeDataObject((DataObject) data, this::newRpcWriter);
index 6e3c7ee08796aab36f0188d6b79e61c4c44ee601..6a5d26ce4b296665a857a402752a21f385c746cc 100644 (file)
@@ -45,6 +45,7 @@ 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.InstanceIdentifier.Item;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
@@ -122,7 +123,14 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         for (final Entry<Class<?>, Method> childDataObj : clsToMethod.entrySet()) {
             final Method method = childDataObj.getValue();
             verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass);
-            final DataContainerCodecPrototype<?> childProto = loadChildPrototype(childDataObj.getKey());
+
+            final Class<?> retClass = childDataObj.getKey();
+            if (OpaqueObject.class.isAssignableFrom(retClass)) {
+                // Filter OpaqueObjects, they are not containers
+                continue;
+            }
+
+            final DataContainerCodecPrototype<?> childProto = loadChildPrototype(retClass);
             tmpMethodToSupplier.put(method, childProto);
             byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
             byYangBuilder.put(childProto.getYangArg(), childProto);
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java
new file mode 100644 (file)
index 0000000..5ba9b79
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import org.opendaylight.yangtools.yang.binding.AbstractOpaqueData;
+import org.opendaylight.yangtools.yang.binding.OpaqueData;
+import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
+
+/**
+ * An {@link OpaqueData} implementation backed by {@link ForeignDataNode}.
+ *
+ * @param <T> Object model type
+ */
+final class ForeignOpaqueData<T> extends AbstractOpaqueData<T> {
+    private final ForeignDataNode<?, T> domData;
+
+    ForeignOpaqueData(final ForeignDataNode<?, T> domData) {
+        this.domData = requireNonNull(domData);
+    }
+
+    @Override
+    public Class<T> getObjectModel() {
+        return domData.getValueObjectModel();
+    }
+
+    @Override
+    public T getData() {
+        return domData.getValue();
+    }
+
+    ForeignDataNode<?, T> domData() {
+        return domData;
+    }
+}
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java
new file mode 100644 (file)
index 0000000..9715f35
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
+import org.opendaylight.yangtools.yang.binding.AbstractOpaqueObject;
+import org.opendaylight.yangtools.yang.binding.OpaqueData;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
+
+/**
+ * Proxy support for an OpaqueObject.
+ *
+ * @param <T> OpaqueObject type
+ */
+// FIXME: MDSAL-442: this class has no run-time dependencies on other generated types, hence is a prime candidate for
+//        becoming a prototype instantiated in a separate ClassLoader (see MDSAL-401) and not being a proxy at all.
+final class ForeignOpaqueObject<T extends OpaqueObject<T>> extends AbstractOpaqueObject<T>
+        implements InvocationHandler {
+    private final @NonNull Class<T> implementedInterface;
+    private final @NonNull ForeignOpaqueData<?> value;
+
+    ForeignOpaqueObject(final Class<T> implementedInterface, final ForeignOpaqueData<?> value) {
+        this.implementedInterface = requireNonNull(implementedInterface);
+        this.value = requireNonNull(value);
+    }
+
+    @Override
+    public Class<T> implementedInterface() {
+        return implementedInterface;
+    }
+
+    @Override
+    public OpaqueData<?> getValue() {
+        return value;
+    }
+
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) {
+        switch (method.getName()) {
+            case "equals":
+                return equals(args[0]);
+            case "hashCode":
+                return hashCode();
+            case "getValue":
+                return getValue();
+            case "toString":
+                return toString();
+            case BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME:
+                return implementedInterface;
+            default:
+                throw new NoSuchMethodError("Unknown method " + method);
+        }
+    }
+}
index 249c49cbb5226302f26f9dd5e9e1695400a5f76e..e628568644d9ba6c3381afba0055315814681528 100644 (file)
@@ -28,7 +28,7 @@ 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;
 
-class LeafNodeCodecContext extends ValueNodeCodecContext {
+class LeafNodeCodecContext extends ValueNodeCodecContext.WithCodec {
     static final class OfTypeObject<T extends TypeObject> extends LeafNodeCodecContext
             implements BindingTypeObjectCodecTreeNode<T> {
         private final @NonNull Class<T> bindingClass;
index e9de07df61f72a662bd17a390cecb5ce1e7a3c02..4d92685ddad8e31001e0a935b19c9e9fc4144278 100644 (file)
@@ -17,7 +17,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 
-final class LeafSetNodeCodecContext extends ValueNodeCodecContext {
+final class LeafSetNodeCodecContext extends ValueNodeCodecContext.WithCodec {
     LeafSetNodeCodecContext(final LeafListSchemaNode schema, final Codec<Object, Object> codec,
         final Method getter) {
         // FIXME: add support for defaults
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java
new file mode 100644 (file)
index 0000000..d14525c
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Throwables;
+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 javax.xml.transform.dom.DOMSource;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
+import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.yangtools.yang.binding.OpaqueData;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNodeCodecContext
+        implements BindingOpaqueObjectCodecTreeNode<T> {
+    static final class AnyXml<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
+        AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class<T> bindingClass) {
+            super(schema, getter, bindingClass);
+        }
+
+        @Override
+        ForeignDataNode<?, ?> serializedData(final OpaqueData<?> opaqueData) {
+            final Class<?> model = opaqueData.getObjectModel();
+            verify(DOMSource.class.isAssignableFrom(model), "Cannot just yet support object model %s", model);
+            return Builders.anyXmlBuilder().withNodeIdentifier(getDomPathArgument())
+                    .withValue((DOMSource) opaqueData.getData()).build();
+        }
+    }
+
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class);
+    private static final MethodType OPAQUEOBJECT_TYPE = MethodType.methodType(OpaqueObject.class,
+        ForeignOpaqueObject.class);
+
+    private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
+        @Override
+        public Object serialize(final Object input) {
+            checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input);
+            // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too
+            final OpaqueData<?> opaqueData = bindingClass.cast(input).getValue();
+            final Object data = opaqueData.getData();
+            checkArgument(data instanceof DOMSource, "Unexpected data %s", data);
+            return data;
+        }
+
+        @Override
+        public Object deserialize(final Object input) {
+            checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
+            return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode<?, ?>) input);
+        }
+    };
+
+    private final MethodHandle proxyConstructor;
+    private final @NonNull Class<T> bindingClass;
+
+    OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class<T> bindingClass) {
+        super(schema, getter, null);
+        this.bindingClass = requireNonNull(bindingClass);
+
+        final Class<?> proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass);
+        try {
+            proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE)
+                    .asType(OPAQUEOBJECT_TYPE);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e);
+        }
+    }
+
+    @Override
+    public final Class<T> getBindingClass() {
+        return bindingClass;
+    }
+
+    @Override
+    public final T deserialize(final NormalizedNode<?, ?> data) {
+        checkArgument(data instanceof ForeignDataNode, "Unexpected value %s", data);
+        final ForeignDataNode<?, ?> foreignData = (ForeignDataNode<?, ?>) data;
+        // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
+        verify(foreignData instanceof AnyXmlNode, "Variable node %s not supported yet", foreignData);
+
+        final ForeignOpaqueData<?> opaqueData = new ForeignOpaqueData<>(foreignData);
+        return bindingClass.cast(createBindingProxy(new ForeignOpaqueObject<>(bindingClass, opaqueData)));
+    }
+
+    @Override
+    public final ForeignDataNode<?, ?> serialize(final T data) {
+        final OpaqueData<?> opaqueData = data.getValue();
+        return opaqueData instanceof ForeignOpaqueData ? ((ForeignOpaqueData<?>) opaqueData).domData()
+                : serializedData(opaqueData);
+    }
+
+    @Override
+    protected final @NonNull Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
+        return deserialize(normalizedNode);
+    }
+
+    @Override
+    Codec<Object, Object> getValueCodec() {
+        return valueCodec;
+    }
+
+    abstract @NonNull ForeignDataNode<?, ?> serializedData(OpaqueData<?> opaqueData);
+
+    @SuppressWarnings("checkstyle:illegalCatch")
+    private OpaqueObject<?> createBindingProxy(final ForeignOpaqueObject<?> handler) {
+        try {
+            return (OpaqueObject<?>) proxyConstructor.invokeExact(handler);
+        } catch (final Throwable e) {
+            Throwables.throwIfUnchecked(e);
+            throw new IllegalStateException(e);
+        }
+    }
+}
index a99ef85a9345c11e7bdbaa0ca27e86916b424283..d6baafe129fde507ffcd001623e044be8ced37d3 100644 (file)
@@ -13,23 +13,35 @@ import java.lang.reflect.Method;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
 /**
  * Abstract base class for atomic nodes. These are nodes which are not decomposed in the Binding Specification, such
  * as LeafNodes and LeafSetNodes.
  */
 abstract class ValueNodeCodecContext extends NodeCodecContext implements NodeContextSupplier {
+    abstract static class WithCodec extends ValueNodeCodecContext {
+        private final @NonNull Codec<Object, Object> valueCodec;
+
+        WithCodec(final DataSchemaNode schema, final Codec<Object, Object> codec, final Method getter,
+                final Object defaultObject) {
+            super(schema, getter, defaultObject);
+            this.valueCodec = requireNonNull(codec);
+        }
+
+        @Override
+        final Codec<Object, Object> getValueCodec() {
+            return valueCodec;
+        }
+    }
+
     private final @NonNull NodeIdentifier yangIdentifier;
-    private final @NonNull Codec<Object, Object> valueCodec;
     private final @NonNull Method getter;
-    private final @NonNull TypedDataSchemaNode schema;
+    private final @NonNull DataSchemaNode schema;
     private final Object defaultObject;
 
-    ValueNodeCodecContext(final TypedDataSchemaNode schema, final Codec<Object, Object> codec,
-            final Method getter, final Object defaultObject) {
+    ValueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Object defaultObject) {
         this.yangIdentifier = NodeIdentifier.create(schema.getQName());
-        this.valueCodec = requireNonNull(codec);
         this.getter = requireNonNull(getter);
         this.schema = requireNonNull(schema);
         this.defaultObject = defaultObject;
@@ -49,12 +61,10 @@ abstract class ValueNodeCodecContext extends NodeCodecContext implements NodeCon
         return getter;
     }
 
-    final Codec<Object, Object> getValueCodec() {
-        return valueCodec;
-    }
+    abstract Codec<Object, Object> getValueCodec();
 
     @Override
-    public final TypedDataSchemaNode getSchema() {
+    public final DataSchemaNode getSchema() {
         return schema;
     }
 
diff --git a/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java b/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java
new file mode 100644 (file)
index 0000000..72ae88f
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+import java.util.Map.Entry;
+import javax.xml.transform.dom.DOMSource;
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.mdsal437.norev.Cont;
+import org.opendaylight.yang.gen.v1.mdsal437.norev.ContBuilder;
+import org.opendaylight.yang.gen.v1.mdsal437.norev.cont.ContAny;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.binding.AbstractOpaqueData;
+import org.opendaylight.yangtools.yang.binding.AbstractOpaqueObject;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.OpaqueData;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class AnyxmlLeafTest extends AbstractBindingCodecTest {
+    private static final NodeIdentifier CONT_NODE_ID = new NodeIdentifier(Cont.QNAME);
+
+    private DOMSource domSource;
+    private ContainerNode cont;
+
+    @Override
+    public void before() {
+        super.before();
+
+        final Document doc = UntrustedXML.newDocumentBuilder().newDocument();
+        final Element element = doc.createElement("foo");
+        domSource = new DOMSource(element);
+
+        cont = Builders.containerBuilder()
+                .withNodeIdentifier(CONT_NODE_ID)
+                .withChild(Builders.anyXmlBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(ContAny.QNAME))
+                    .withValue(domSource)
+                    .build())
+                .build();
+    }
+
+    @Test
+    public void testAnyxmlToBinding() {
+        final Entry<InstanceIdentifier<?>, DataObject> entry = registry.fromNormalizedNode(
+            YangInstanceIdentifier.create(CONT_NODE_ID), cont);
+        assertEquals(InstanceIdentifier.create(Cont.class), entry.getKey());
+        final DataObject ldo = entry.getValue();
+        assertThat(ldo, instanceOf(Cont.class));
+
+        // So no... GrpAny should be null ..
+        final Cont contValue = (Cont) ldo;
+        assertNull(contValue.getGrpAny());
+
+        // ContAny is interesting
+        final ContAny anyCont = contValue.getContAny();
+        assertNotNull(anyCont);
+        assertEquals(ContAny.class, anyCont.implementedInterface());
+
+        final OpaqueData<?> value = anyCont.getValue();
+        assertNotNull(value);
+        assertEquals(DOMSource.class, value.getObjectModel());
+        assertSame(domSource, value.getData());
+
+        // Stable hashCode
+        final int hashOne = anyCont.hashCode();
+        final int hashTwo = anyCont.hashCode();
+        assertEquals(hashOne, hashTwo);
+
+        // Basic equality
+        assertNotEquals(anyCont, null);
+        assertEquals(anyCont, anyCont);
+        assertEquals(new FakeCont(), anyCont);
+        assertEquals(anyCont, new FakeCont());
+        assertNotEquals(anyCont, new TestNormalizedNodeCont());
+        assertNotEquals(new TestNormalizedNodeCont(), anyCont);
+    }
+
+    @Test
+    public void testAnyxmlFromBinding() {
+        final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry = registry.toNormalizedNode(
+            InstanceIdentifier.create(Cont.class), new ContBuilder().setContAny(new FakeCont()).build());
+        assertEquals(YangInstanceIdentifier.create(CONT_NODE_ID), entry.getKey());
+        assertEquals(cont, entry.getValue());
+    }
+
+    private final class FakeData extends AbstractOpaqueData<DOMSource> {
+        @Override
+        public Class<DOMSource> getObjectModel() {
+            return DOMSource.class;
+        }
+
+        @Override
+        public DOMSource getData() {
+            return domSource;
+        }
+    }
+
+    private abstract static class AbstractTestCont extends AbstractOpaqueObject<ContAny> implements ContAny {
+
+    }
+
+    private final class FakeCont extends AbstractTestCont {
+        @Override
+        public OpaqueData<?> getValue() {
+            return new FakeData();
+        }
+    }
+
+    private final class TestNormalizedNodeCont extends AbstractTestCont {
+        @Override
+        public OpaqueData<?> getValue() {
+            return new AbstractOpaqueData<NormalizedNode>() {
+
+                @Override
+                public Class<NormalizedNode> getObjectModel() {
+                    return NormalizedNode.class;
+                }
+
+                @Override
+                public NormalizedNode getData() {
+                    return cont;
+                }
+            };
+        }
+    }
+}