From: Robert Varga Date: Mon, 15 Apr 2019 08:02:03 +0000 (+0200) Subject: Generate OpaqueObject implementation X-Git-Tag: v4.0.1~33 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=9ceab95fc804f9042da6088d843c8cd3a57aed29;p=mdsal.git Generate OpaqueObject implementation 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 --- diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java index 83cc618092..f8f06fbea7 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java @@ -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 loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) typeDef, + final Callable 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 index 0000000000..a1a9616c28 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecOpaqueObject.java @@ -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 OpaqueObject type + */ +@Beta +public abstract class CodecOpaqueObject> extends AbstractOpaqueObject { + 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 index 9715f355e7..0000000000 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java +++ /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 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> extends AbstractOpaqueObject - implements InvocationHandler { - private final @NonNull Class implementedInterface; - private final @NonNull ForeignOpaqueData value; - - ForeignOpaqueObject(final Class implementedInterface, final ForeignOpaqueData value) { - this.implementedInterface = requireNonNull(implementedInterface); - this.value = requireNonNull(value); - } - - @Override - public Class 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); - } - } -} 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 index d14525c820..a64145e11c 100644 --- 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 @@ -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> extends ValueNodeCodecContext implements BindingOpaqueObjectCodecTreeNode { static final class AnyXml> extends OpaqueNodeCodecContext { - AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class bindingClass) { - super(schema, getter, bindingClass); + AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class bindingClass, + final CodecClassLoader loader) { + super(schema, getter, bindingClass, loader); } @Override @@ -47,9 +54,9 @@ abstract class OpaqueNodeCodecContext> 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 valueCodec = new Codec() { @Override @@ -72,17 +79,11 @@ abstract class OpaqueNodeCodecContext> extends ValueNo private final MethodHandle proxyConstructor; private final @NonNull Class bindingClass; - OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class bindingClass) { + OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class 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> 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> 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); + } + } }