Bump to odlparent-9.0.0/yangtools-7.0.1-SNAPSHOT
[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.AbstractIllegalArgumentCodec;
28 import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
29 import org.opendaylight.yangtools.yang.binding.OpaqueData;
30 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
31 import org.opendaylight.yangtools.yang.data.api.schema.AnydataNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
35 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
36 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
39
40 abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNodeCodecContext
41         implements BindingOpaqueObjectCodecTreeNode<T> {
42     static final class Anyxml<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
43         Anyxml(final AnyxmlSchemaNode schema, final String getterName, final Class<T> bindingClass,
44                 final CodecClassLoader loader) {
45             super(schema, getterName, bindingClass, loader);
46         }
47
48         @Override
49         ForeignDataNode<?> serializedData(final OpaqueData<?> opaqueData) {
50             final Class<?> model = opaqueData.getObjectModel();
51             verify(DOMSource.class.isAssignableFrom(model), "Cannot just yet support object model %s", model);
52             return Builders.anyXmlBuilder().withNodeIdentifier(getDomPathArgument())
53                     .withValue((DOMSource) opaqueData.getData()).build();
54         }
55
56         @Override
57         T deserialize(final ForeignDataNode<?> foreignData) {
58             // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
59             verify(foreignData instanceof DOMSourceAnyxmlNode, "Variable node %s not supported yet", foreignData);
60             return super.deserialize(foreignData);
61         }
62     }
63
64     static final class Anydata<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
65         Anydata(final AnydataSchemaNode schema, final String getterName, final Class<T> bindingClass,
66                 final CodecClassLoader loader) {
67             super(schema, getterName, bindingClass, loader);
68         }
69
70         @Override
71         AnydataNode<?> serializedData(final OpaqueData<?> opaqueData) {
72             return buildAnydata(opaqueData);
73         }
74
75         private <M> @NonNull AnydataNode<M> buildAnydata(final OpaqueData<M> opaqueData) {
76             return Builders.anydataBuilder(opaqueData.getObjectModel()).withNodeIdentifier(getDomPathArgument())
77                     .withValue(opaqueData.getData()).build();
78         }
79     }
80
81     private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(OpaqueObject.class,
82         OpaqueData.class);
83     @SuppressWarnings("rawtypes")
84     private static final Builder<CodecOpaqueObject> TEMPLATE = new ByteBuddy().subclass(CodecOpaqueObject.class)
85             .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC);
86
87     private final IllegalArgumentCodec<Object, Object> valueCodec = new AbstractIllegalArgumentCodec<>() {
88         @Override
89         protected Object serializeImpl(final Object input) {
90             checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input);
91             // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too
92             final OpaqueData<?> opaqueData = bindingClass.cast(input).getValue();
93             final Object data = opaqueData.getData();
94             checkArgument(data instanceof DOMSource, "Unexpected data %s", data);
95             return data;
96         }
97
98         @Override
99         protected Object deserializeImpl(final Object input) {
100             checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
101             return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode) input);
102         }
103     };
104
105     private final MethodHandle proxyConstructor;
106     private final @NonNull Class<T> bindingClass;
107
108     OpaqueNodeCodecContext(final DataSchemaNode schema, final String getterName, final Class<T> bindingClass,
109             final CodecClassLoader loader) {
110         super(schema, getterName, null);
111         this.bindingClass = requireNonNull(bindingClass);
112         proxyConstructor = createImpl(loader, bindingClass);
113     }
114
115     @Override
116     public final Class<T> getBindingClass() {
117         return bindingClass;
118     }
119
120     @Override
121     public final T deserialize(final NormalizedNode data) {
122         checkArgument(data instanceof ForeignDataNode, "Unexpected value %s", data);
123         return deserialize((ForeignDataNode<?>) data);
124     }
125
126     T deserialize(final ForeignDataNode<?> foreignData) {
127         return bindingClass.cast(createBindingProxy(new ForeignOpaqueData<>(foreignData)));
128     }
129
130     @Override
131     public final ForeignDataNode<?> serialize(final T data) {
132         final OpaqueData<?> opaqueData = data.getValue();
133         return opaqueData instanceof ForeignOpaqueData ? ((ForeignOpaqueData<?>) opaqueData).domData()
134                 : serializedData(opaqueData);
135     }
136
137     @Override
138     protected final @NonNull Object deserializeObject(final NormalizedNode normalizedNode) {
139         return deserialize(normalizedNode);
140     }
141
142     @Override
143     IllegalArgumentCodec<Object, Object> getValueCodec() {
144         return valueCodec;
145     }
146
147     abstract @NonNull ForeignDataNode<?> serializedData(OpaqueData<?> opaqueData);
148
149     @SuppressWarnings("checkstyle:illegalCatch")
150     private OpaqueObject<?> createBindingProxy(final OpaqueData<?> data) {
151         try {
152             return (OpaqueObject<?>) proxyConstructor.invokeExact(data);
153         } catch (final Throwable e) {
154             Throwables.throwIfUnchecked(e);
155             throw new IllegalStateException(e);
156         }
157     }
158
159     private static MethodHandle createImpl(final CodecClassLoader rootLoader, final Class<?> bindingClass) {
160         final Class<?> proxyClass = rootLoader.generateClass(bindingClass, "codecImpl",
161             (loader, fqcn, bindingInterface) -> GeneratorResult.of(TEMPLATE
162                 .name(fqcn)
163                 .implement(bindingInterface)
164                 .make()));
165
166         Constructor<?> ctor;
167         try {
168             ctor = proxyClass.getDeclaredConstructor(OpaqueData.class);
169         } catch (NoSuchMethodException e) {
170             throw new LinkageError("Failed to acquire constructor for prototype " + proxyClass, e);
171         }
172         try {
173             return MethodHandles.publicLookup().unreflectConstructor(ctor).asType(CONSTRUCTOR_TYPE);
174         } catch (IllegalAccessException e) {
175             throw new LinkageError("Failed to access constructor for prototype " + proxyClass, e);
176         }
177     }
178 }