Generate OpaqueObject implementation 53/81653/19
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 15 Apr 2019 08:02:03 +0000 (10:02 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 18 Apr 2019 16:36:25 +0000 (18:36 +0200)
With the advent of CodecClassLoader we can safely instantiate
codec-specific implementations of binding interfaces instead
of relying on dynamic proxies.

This has several advantages:
- we do not generate a proxy class in public space, hence our mess
  will undergo normal GC rules
- generated code is a normal final class, hence we can inherit
  method implementations from interfaces and subclasses
- for each instance, we do not require a proxy and an invocation
  handler, so we instantiate only one object
- there is no reflection in the invocation path, hence JIT has
  full (and direct) visibility and can perform all of its magic

This patch does that for OpaqueObject, which results extremely
simple generated classes: they just subclass a ForeignOpaqueObject
and add the target binding interface.

JIRA: MDSAL-442
Change-Id: I2be5bb88f9557375f517415d6bec9f68e803628c
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
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/CodecOpaqueObject.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java [deleted file]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java

index 83cc618092afbb4c6803679a1bc037f2256f832e..f8f06fbea76dea83f7b3f3d9c63b934d39398cbe 100644 (file)
@@ -31,6 +31,8 @@ import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
 import org.opendaylight.mdsal.binding.dom.codec.util.BindingSchemaMapping;
 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
@@ -71,6 +73,7 @@ import org.slf4j.LoggerFactory;
 final class BindingCodecContext implements CodecContextFactory, BindingCodecTree, Immutable {
     private static final Logger LOG = LoggerFactory.getLogger(BindingCodecContext.class);
 
+    private final CodecClassLoader loader = StaticClassPool.createLoader();
     private final InstanceIdentifierCodec instanceIdentifierCodec;
     private final IdentityCodec identityCodec;
     private final BindingNormalizedNodeCodecRegistry registry;
@@ -284,7 +287,7 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
                     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));
+                            valueType.asSubclass(OpaqueObject.class), loader);
                 } else {
                     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).
@@ -319,10 +322,10 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
         } else if (typeDef instanceof InstanceIdentifierTypeDefinition) {
             return ValueTypeCodec.encapsulatedValueCodecFor(valueType, typeDef, instanceIdentifierCodec);
         } else if (typeDef instanceof UnionTypeDefinition) {
-            final Callable<UnionTypeCodec> loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) typeDef,
+            final Callable<UnionTypeCodec> unionLoader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) typeDef,
                 this);
             try {
-                return loader.call();
+                return unionLoader.call();
             } catch (final Exception e) {
                 throw new IllegalStateException("Unable to load codec for " + valueType, e);
             }
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecOpaqueObject.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecOpaqueObject.java
new file mode 100644 (file)
index 0000000..a1a9616
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.binding.AbstractOpaqueObject;
+import org.opendaylight.yangtools.yang.binding.OpaqueData;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
+
+/**
+ * A base class for {@link OpaqueObject}s backed by {@link ForeignOpaqueData}. While this class is public, it not part
+ * of API surface and is an implementation detail. The only reason for it being public is that it needs to be accessible
+ * by code generated at runtime.
+ *
+ * @param <T> OpaqueObject type
+ */
+@Beta
+public abstract class CodecOpaqueObject<T extends OpaqueObject<T>> extends AbstractOpaqueObject<T> {
+    private final @NonNull OpaqueData<?> value;
+
+    // This constructor is public so Javassist generates a public constructor and we do not need to muck
+    public CodecOpaqueObject(final OpaqueData<?> value) {
+        this.value = requireNonNull(value);
+    }
+
+    @Override
+    public final OpaqueData<?> getValue() {
+        return value;
+    }
+}
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
deleted file mode 100644 (file)
index 9715f35..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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 d14525c820bd78ff9c8041bc24d10515d0856029..a64145e11c17ad41acd865bcb2e9d2990778ef8e 100644 (file)
@@ -12,15 +12,21 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Throwables;
+import java.io.IOException;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.Modifier;
+import javassist.NotFoundException;
 import javax.xml.transform.dom.DOMSource;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.binding.OpaqueData;
 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
@@ -34,8 +40,9 @@ 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);
+        AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class<T> bindingClass,
+                final CodecClassLoader loader) {
+            super(schema, getter, bindingClass, loader);
         }
 
         @Override
@@ -47,9 +54,9 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
         }
     }
 
-    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 CtClass SUPERCLASS = StaticClassPool.findClass(CodecOpaqueObject.class);
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(OpaqueObject.class,
+        OpaqueData.class);
 
     private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
         @Override
@@ -72,17 +79,11 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
     private final MethodHandle proxyConstructor;
     private final @NonNull Class<T> bindingClass;
 
-    OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class<T> bindingClass) {
+    OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class<T> bindingClass,
+            final CodecClassLoader loader) {
         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);
-        }
+        proxyConstructor = createImpl(loader, bindingClass);
     }
 
     @Override
@@ -97,8 +98,7 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
         // 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)));
+        return bindingClass.cast(createBindingProxy(new ForeignOpaqueData<>(foreignData)));
     }
 
     @Override
@@ -121,12 +121,37 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
     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;
+        try {
+            proxyClass = rootLoader.generateSubclass(SUPERCLASS, bindingClass, "codecImpl",
+                (pool, binding, generated) -> {
+                    generated.addInterface(binding);
+                    generated.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
+                });
+        } catch (CannotCompileException | IOException | NotFoundException e) {
+            throw new LinkageError("Failed to instantiate prototype for " + bindingClass, e);
+        }
+
+        Constructor<?> ctor;
+        try {
+            ctor = proxyClass.getDeclaredConstructor(OpaqueData.class);
+        } catch (NoSuchMethodException e) {
+            throw new LinkageError("Failed to acquire constructor for prototype " + proxyClass, e);
+        }
+        try {
+            return MethodHandles.publicLookup().unreflectConstructor(ctor).asType(CONSTRUCTOR_TYPE);
+        } catch (IllegalAccessException e) {
+            throw new LinkageError("Failed to access constructor for prototype " + proxyClass, e);
+        }
+    }
 }