Disconnect EncapsulatedValueCodec from ReflectionBasedCodec
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / OpaqueNodeCodecContext.java
index d14525c820bd78ff9c8041bc24d10515d0856029..f26fb25bd0011fded232834f4c5dd8d1e945e076 100644 (file)
@@ -15,45 +15,77 @@ 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 net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType.Builder;
+import net.bytebuddy.jar.asm.Opcodes;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
-import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
+import org.opendaylight.yangtools.concepts.AbstractIllegalArgumentCodec;
+import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
 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.AnydataNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
 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.AnydataSchemaNode;
+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);
+    static final class Anyxml<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
+        Anyxml(final AnyxmlSchemaNode schema, final String getterName, final Class<T> bindingClass,
+                final CodecClassLoader loader) {
+            super(schema, getterName, bindingClass, loader);
         }
 
         @Override
-        ForeignDataNode<?, ?> serializedData(final OpaqueData<?> opaqueData) {
+        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();
         }
+
+        @Override
+        T deserialize(final ForeignDataNode<?> foreignData) {
+            // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
+            verify(foreignData instanceof DOMSourceAnyxmlNode, "Variable node %s not supported yet", foreignData);
+            return super.deserialize(foreignData);
+        }
+    }
+
+    static final class Anydata<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
+        Anydata(final AnydataSchemaNode schema, final String getterName, final Class<T> bindingClass,
+                final CodecClassLoader loader) {
+            super(schema, getterName, bindingClass, loader);
+        }
+
+        @Override
+        AnydataNode<?> serializedData(final OpaqueData<?> opaqueData) {
+            return buildAnydata(opaqueData);
+        }
+
+        private <M> @NonNull AnydataNode<M> buildAnydata(final OpaqueData<M> opaqueData) {
+            return Builders.anydataBuilder(opaqueData.getObjectModel()).withNodeIdentifier(getDomPathArgument())
+                    .withValue(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 static final MethodType CTOR_LOOKUP_TYPE = MethodType.methodType(void.class, OpaqueData.class);
+    private static final MethodType CTOR_INVOKE_TYPE = MethodType.methodType(OpaqueObject.class, OpaqueData.class);
+    @SuppressWarnings("rawtypes")
+    private static final Builder<CodecOpaqueObject> TEMPLATE = new ByteBuddy().subclass(CodecOpaqueObject.class)
+            .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC);
 
-    private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
+    private final IllegalArgumentCodec<Object, Object> valueCodec = new AbstractIllegalArgumentCodec<>() {
         @Override
-        public Object serialize(final Object input) {
+        protected Object serializeImpl(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();
@@ -63,26 +95,20 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
         }
 
         @Override
-        public Object deserialize(final Object input) {
+        protected Object deserializeImpl(final Object input) {
             checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
-            return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode<?, ?>) 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);
+    OpaqueNodeCodecContext(final DataSchemaNode schema, final String getterName, final Class<T> bindingClass,
+            final CodecClassLoader loader) {
+        super(schema, getterName, 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);
-        }
+        proxyConstructor = createImpl(loader, bindingClass);
     }
 
     @Override
@@ -91,42 +117,55 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
     }
 
     @Override
-    public final T deserialize(final NormalizedNode<?, ?> data) {
+    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);
+        return deserialize((ForeignDataNode<?>) data);
+    }
 
-        final ForeignOpaqueData<?> opaqueData = new ForeignOpaqueData<>(foreignData);
-        return bindingClass.cast(createBindingProxy(new ForeignOpaqueObject<>(bindingClass, opaqueData)));
+    T deserialize(final ForeignDataNode<?> foreignData) {
+        return bindingClass.cast(createBindingProxy(new ForeignOpaqueData<>(foreignData)));
     }
 
     @Override
-    public final ForeignDataNode<?, ?> serialize(final T data) {
+    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) {
+    protected final @NonNull Object deserializeObject(final NormalizedNode normalizedNode) {
         return deserialize(normalizedNode);
     }
 
     @Override
-    Codec<Object, Object> getValueCodec() {
+    IllegalArgumentCodec<Object, Object> getValueCodec() {
         return valueCodec;
     }
 
-    abstract @NonNull ForeignDataNode<?, ?> serializedData(OpaqueData<?> opaqueData);
+    abstract @NonNull ForeignDataNode<?> serializedData(OpaqueData<?> opaqueData);
 
     @SuppressWarnings("checkstyle:illegalCatch")
-    private OpaqueObject<?> createBindingProxy(final ForeignOpaqueObject<?> handler) {
+    private OpaqueObject<?> createBindingProxy(final OpaqueData<?> data) {
         try {
-            return (OpaqueObject<?>) proxyConstructor.invokeExact(handler);
+            return (OpaqueObject<?>) proxyConstructor.invokeExact(data);
         } catch (final Throwable e) {
             Throwables.throwIfUnchecked(e);
             throw new IllegalStateException(e);
         }
     }
+
+    private static MethodHandle createImpl(final CodecClassLoader rootLoader, final Class<?> bindingClass) {
+        final Class<?> proxyClass = rootLoader.generateClass(bindingClass, "codecImpl",
+            (loader, fqcn, bindingInterface) -> GeneratorResult.of(TEMPLATE
+                .name(fqcn)
+                .implement(bindingInterface)
+                .make()));
+
+        try {
+            return MethodHandles.publicLookup().findConstructor(proxyClass, CTOR_LOOKUP_TYPE).asType(CTOR_INVOKE_TYPE);
+        } catch (IllegalAccessException | NoSuchMethodException e) {
+            throw new LinkageError("Failed to access constructor for prototype " + proxyClass, e);
+        }
+    }
 }