Do not retain java.lang.reflect.Method in ValueNodeCodecContext
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / OpaqueNodeCodecContext.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.base.Throwables;
15 import java.lang.invoke.MethodHandle;
16 import java.lang.invoke.MethodHandles;
17 import java.lang.invoke.MethodType;
18 import java.lang.reflect.Constructor;
19 import javax.xml.transform.dom.DOMSource;
20 import net.bytebuddy.ByteBuddy;
21 import net.bytebuddy.dynamic.DynamicType.Builder;
22 import net.bytebuddy.jar.asm.Opcodes;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
25 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
26 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
27 import org.opendaylight.yangtools.concepts.Codec;
28 import org.opendaylight.yangtools.yang.binding.OpaqueData;
29 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
30 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
34 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36
37 abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNodeCodecContext
38         implements BindingOpaqueObjectCodecTreeNode<T> {
39     static final class AnyXml<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
40         AnyXml(final AnyXmlSchemaNode schema, final String getterName, final Class<T> bindingClass,
41                 final CodecClassLoader loader) {
42             super(schema, getterName, bindingClass, loader);
43         }
44
45         @Override
46         ForeignDataNode<?, ?> serializedData(final OpaqueData<?> opaqueData) {
47             final Class<?> model = opaqueData.getObjectModel();
48             verify(DOMSource.class.isAssignableFrom(model), "Cannot just yet support object model %s", model);
49             return Builders.anyXmlBuilder().withNodeIdentifier(getDomPathArgument())
50                     .withValue((DOMSource) opaqueData.getData()).build();
51         }
52     }
53
54     private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(OpaqueObject.class,
55         OpaqueData.class);
56     @SuppressWarnings("rawtypes")
57     private static final Builder<CodecOpaqueObject> TEMPLATE = new ByteBuddy().subclass(CodecOpaqueObject.class)
58             .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC);
59
60     private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
61         @Override
62         public Object serialize(final Object input) {
63             checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input);
64             // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too
65             final OpaqueData<?> opaqueData = bindingClass.cast(input).getValue();
66             final Object data = opaqueData.getData();
67             checkArgument(data instanceof DOMSource, "Unexpected data %s", data);
68             return data;
69         }
70
71         @Override
72         public Object deserialize(final Object input) {
73             checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
74             return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode<?, ?>) input);
75         }
76     };
77
78     private final MethodHandle proxyConstructor;
79     private final @NonNull Class<T> bindingClass;
80
81     OpaqueNodeCodecContext(final DataSchemaNode schema, final String getterName, final Class<T> bindingClass,
82             final CodecClassLoader loader) {
83         super(schema, getterName, null);
84         this.bindingClass = requireNonNull(bindingClass);
85         proxyConstructor = createImpl(loader, bindingClass);
86     }
87
88     @Override
89     public final Class<T> getBindingClass() {
90         return bindingClass;
91     }
92
93     @Override
94     public final T deserialize(final NormalizedNode<?, ?> data) {
95         checkArgument(data instanceof ForeignDataNode, "Unexpected value %s", data);
96         final ForeignDataNode<?, ?> foreignData = (ForeignDataNode<?, ?>) data;
97         // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
98         verify(foreignData instanceof AnyXmlNode, "Variable node %s not supported yet", foreignData);
99
100         return bindingClass.cast(createBindingProxy(new ForeignOpaqueData<>(foreignData)));
101     }
102
103     @Override
104     public final ForeignDataNode<?, ?> serialize(final T data) {
105         final OpaqueData<?> opaqueData = data.getValue();
106         return opaqueData instanceof ForeignOpaqueData ? ((ForeignOpaqueData<?>) opaqueData).domData()
107                 : serializedData(opaqueData);
108     }
109
110     @Override
111     protected final @NonNull Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
112         return deserialize(normalizedNode);
113     }
114
115     @Override
116     Codec<Object, Object> getValueCodec() {
117         return valueCodec;
118     }
119
120     abstract @NonNull ForeignDataNode<?, ?> serializedData(OpaqueData<?> opaqueData);
121
122     @SuppressWarnings("checkstyle:illegalCatch")
123     private OpaqueObject<?> createBindingProxy(final OpaqueData<?> data) {
124         try {
125             return (OpaqueObject<?>) proxyConstructor.invokeExact(data);
126         } catch (final Throwable e) {
127             Throwables.throwIfUnchecked(e);
128             throw new IllegalStateException(e);
129         }
130     }
131
132     private static MethodHandle createImpl(final CodecClassLoader rootLoader, final Class<?> bindingClass) {
133         final Class<?> proxyClass = rootLoader.generateClass(bindingClass, "codecImpl",
134             (loader, fqcn, bindingInterface) -> GeneratorResult.of(TEMPLATE
135                 .name(fqcn)
136                 .implement(bindingInterface)
137                 .make()));
138
139         Constructor<?> ctor;
140         try {
141             ctor = proxyClass.getDeclaredConstructor(OpaqueData.class);
142         } catch (NoSuchMethodException e) {
143             throw new LinkageError("Failed to acquire constructor for prototype " + proxyClass, e);
144         }
145         try {
146             return MethodHandles.publicLookup().unreflectConstructor(ctor).asType(CONSTRUCTOR_TYPE);
147         } catch (IllegalAccessException e) {
148             throw new LinkageError("Failed to access constructor for prototype " + proxyClass, e);
149         }
150     }
151 }