Seal NodeCodecContext hierarchy
[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 javax.xml.transform.dom.DOMSource;
19 import net.bytebuddy.ByteBuddy;
20 import net.bytebuddy.dynamic.DynamicType.Builder;
21 import net.bytebuddy.jar.asm.Opcodes;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
24 import org.opendaylight.mdsal.binding.loader.BindingClassLoader;
25 import org.opendaylight.mdsal.binding.loader.BindingClassLoader.GeneratorResult;
26 import org.opendaylight.yangtools.yang.binding.OpaqueData;
27 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
28 import org.opendaylight.yangtools.yang.data.api.schema.AnydataNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
33 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36
37 abstract sealed 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 BindingClassLoader 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         @Override
54         T deserialize(final ForeignDataNode<?> foreignData) {
55             // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
56             verify(foreignData instanceof DOMSourceAnyxmlNode, "Variable node %s not supported yet", foreignData);
57             return super.deserialize(foreignData);
58         }
59     }
60
61     static final class Anydata<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
62         Anydata(final AnydataSchemaNode schema, final String getterName, final Class<T> bindingClass,
63                 final BindingClassLoader loader) {
64             super(schema, getterName, bindingClass, loader);
65         }
66
67         @Override
68         AnydataNode<?> serializedData(final OpaqueData<?> opaqueData) {
69             return buildAnydata(opaqueData);
70         }
71
72         private <M> @NonNull AnydataNode<M> buildAnydata(final OpaqueData<M> opaqueData) {
73             return Builders.anydataBuilder(opaqueData.getObjectModel()).withNodeIdentifier(getDomPathArgument())
74                     .withValue(opaqueData.getData()).build();
75         }
76     }
77
78     private static final MethodType CTOR_LOOKUP_TYPE = MethodType.methodType(void.class, OpaqueData.class);
79     private static final MethodType CTOR_INVOKE_TYPE = MethodType.methodType(OpaqueObject.class, OpaqueData.class);
80     @SuppressWarnings("rawtypes")
81     private static final Builder<CodecOpaqueObject> TEMPLATE = new ByteBuddy().subclass(CodecOpaqueObject.class)
82             .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC);
83
84     private final AbstractValueCodec<Object, Object> valueCodec = new AbstractValueCodec<>() {
85         @Override
86         protected Object serializeImpl(final Object input) {
87             checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input);
88             // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too
89             final OpaqueData<?> opaqueData = bindingClass.cast(input).getValue();
90             final Object data = opaqueData.getData();
91             checkArgument(data instanceof DOMSource, "Unexpected data %s", data);
92             return data;
93         }
94
95         @Override
96         protected Object deserializeImpl(final Object input) {
97             checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
98             return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode) input);
99         }
100     };
101
102     private final MethodHandle proxyConstructor;
103     private final @NonNull Class<T> bindingClass;
104
105     OpaqueNodeCodecContext(final DataSchemaNode schema, final String getterName, final Class<T> bindingClass,
106             final BindingClassLoader loader) {
107         super(schema, getterName, null);
108         this.bindingClass = requireNonNull(bindingClass);
109         proxyConstructor = createImpl(loader, bindingClass);
110     }
111
112     @Override
113     public final Class<T> getBindingClass() {
114         return bindingClass;
115     }
116
117     @Override
118     public final T deserialize(final NormalizedNode data) {
119         if (data instanceof ForeignDataNode<?> foreign) {
120             return deserialize(foreign);
121         }
122         throw new IllegalArgumentException("Expected a ForeignDataNode, not " + data.contract().getSimpleName());
123     }
124
125     T deserialize(final ForeignDataNode<?> foreignData) {
126         return bindingClass.cast(createBindingProxy(new ForeignOpaqueData<>(foreignData)));
127     }
128
129     @Override
130     public final ForeignDataNode<?> serialize(final T data) {
131         final OpaqueData<?> opaqueData = data.getValue();
132         return opaqueData instanceof ForeignOpaqueData<?> foreign ? foreign.domData() : serializedData(opaqueData);
133     }
134
135     @Override
136     protected final @NonNull Object deserializeObject(final NormalizedNode normalizedNode) {
137         return deserialize(normalizedNode);
138     }
139
140     @Override
141     ValueCodec<Object, Object> getValueCodec() {
142         return valueCodec;
143     }
144
145     abstract @NonNull ForeignDataNode<?> serializedData(OpaqueData<?> opaqueData);
146
147     @SuppressWarnings("checkstyle:illegalCatch")
148     private OpaqueObject<?> createBindingProxy(final OpaqueData<?> data) {
149         try {
150             return (OpaqueObject<?>) proxyConstructor.invokeExact(data);
151         } catch (final Throwable e) {
152             Throwables.throwIfUnchecked(e);
153             throw new IllegalStateException(e);
154         }
155     }
156
157     private static MethodHandle createImpl(final BindingClassLoader rootLoader, final Class<?> bindingClass) {
158         final Class<?> proxyClass = CodecPackage.CODEC.generateClass(rootLoader, bindingClass,
159             (loader, fqcn, bindingInterface) -> GeneratorResult.of(TEMPLATE
160                 .name(fqcn)
161                 .implement(bindingInterface)
162                 .make()));
163
164         try {
165             return MethodHandles.publicLookup().findConstructor(proxyClass, CTOR_LOOKUP_TYPE).asType(CTOR_INVOKE_TYPE);
166         } catch (IllegalAccessException | NoSuchMethodException e) {
167             throw new LinkageError("Failed to access constructor for prototype " + proxyClass, e);
168         }
169     }
170 }