Binding v2 DOM Codec - generator - SPI - part 1
[mdsal.git] / binding2 / mdsal-binding2-dom-codec / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / codec / generator / spi / generator / AbstractStreamWriterGenerator.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies 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.javav2.dom.codec.generator.spi.generator;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Supplier;
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import java.lang.reflect.Field;
17 import java.lang.reflect.InvocationTargetException;
18 import java.util.Map.Entry;
19 import javassist.CannotCompileException;
20 import javassist.CtClass;
21 import javassist.CtField;
22 import javassist.CtMethod;
23 import javassist.Modifier;
24 import javassist.NotFoundException;
25 import javax.annotation.Nonnull;
26 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.api.TreeNodeSerializerGenerator;
27 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.StaticBindingProperty;
28 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.TreeNodeSerializerPrototype;
29 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.spi.source.AbstractTreeNodeSerializerSource;
30 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.serializer.AugmentableDispatchSerializer;
31 import org.opendaylight.mdsal.binding.javav2.generator.impl.util.BindingRuntimeContext;
32 import org.opendaylight.mdsal.binding.javav2.generator.impl.util.javassist.JavassistUtils;
33 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
34 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
35 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
36 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
37 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
38 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerImplementation;
39 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerRegistry;
40 import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections;
41 import org.opendaylight.yangtools.util.ClassLoaderUtils;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
43 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
44 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 @Beta
51 public abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements TreeNodeSerializerGenerator {
52
53     private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
54
55     public static final String SERIALIZE_METHOD_NAME = "serialize";
56     protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
57     private static final Field FIELD_MODIFIERS;
58
59     private final LoadingCache<Class<?>, TreeNodeSerializerImplementation> implementations;
60     private final CtClass[] serializeArguments;
61     private final JavassistUtils javassist;
62
63     private BindingRuntimeContext context;
64
65     static {
66         /*
67          * Cache reflection access to field modifiers field. We need this to set
68          * fix the static declared fields to final once we initialize them. If
69          * we cannot get access, that's fine, too.
70          */
71         Field f = null;
72         try {
73             f = Field.class.getDeclaredField("modifiers");
74             f.setAccessible(true);
75         } catch (NoSuchFieldException | SecurityException e) {
76             LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
77         }
78
79         FIELD_MODIFIERS = f;
80     }
81
82     protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
83         super();
84         this.javassist = Preconditions.checkNotNull(utils, "JavassistUtils instance is required.");
85         this.serializeArguments = new CtClass[] { javassist.asCtClass(TreeNodeSerializerRegistry.class),
86                 javassist.asCtClass(TreeNode.class), javassist.asCtClass(BindingStreamEventWriter.class), };
87         javassist.appendClassLoaderIfMissing(TreeNodeSerializerPrototype.class.getClassLoader());
88         this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
89     }
90
91     @Override
92     public final TreeNodeSerializerImplementation getSerializer(final Class<?> type) {
93         return implementations.getUnchecked(type);
94     }
95
96     @Override
97     public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
98         this.context = runtime;
99     }
100
101     @Override
102     public final String loadSerializerFor(final Class<?> cls) {
103         return implementations.getUnchecked(cls).getClass().getName();
104     }
105
106     private final class SerializerImplementationLoader extends CacheLoader<Class<?>, TreeNodeSerializerImplementation> {
107
108         private static final String GETINSTANCE_METHOD_NAME = "getInstance";
109         private static final String SERIALIZER_SUFFIX = "$StreamWriter";
110
111         private String getSerializerName(final Class<?> type) {
112             return type.getName() + SERIALIZER_SUFFIX;
113         }
114
115         @Override
116         @SuppressWarnings("unchecked")
117         public TreeNodeSerializerImplementation load(@Nonnull final Class<?> type) throws Exception {
118             Preconditions.checkArgument(BindingReflections.isBindingClass(type));
119             Preconditions.checkArgument(Instantiable.class.isAssignableFrom(type),
120                     "Instantiable is not assingnable from %s from classloader %s.", type, type.getClassLoader());
121
122             final String serializerName = getSerializerName(type);
123
124             Class<? extends TreeNodeSerializerImplementation> cls;
125             try {
126                 cls = (Class<? extends TreeNodeSerializerImplementation>) ClassLoaderUtils
127                         .loadClass(type.getClassLoader(), serializerName);
128             } catch (final ClassNotFoundException e) {
129                 cls = generateSerializer(type, serializerName);
130             }
131
132             final TreeNodeSerializerImplementation obj =
133                     (TreeNodeSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
134             LOG.debug("Loaded serializer {} for class {}", obj, type);
135             return obj;
136         }
137
138         @SuppressWarnings("unchecked")
139         private Class<? extends TreeNodeSerializerImplementation> generateSerializer(final Class<?> type,
140                 final String serializerName)
141                 throws CannotCompileException, IllegalAccessException, IllegalArgumentException,
142                 InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
143             final AbstractTreeNodeSerializerSource source = generateEmitterSource(type, serializerName);
144             final CtClass poolClass = generateEmitter0(type, source, serializerName);
145             final Class<? extends TreeNodeSerializerImplementation> cls =
146                     poolClass.toClass(type.getClassLoader(), type.getProtectionDomain());
147
148             /*
149              * Due to OSGi class loader rules we cannot initialize the fields
150              * during construction, as the initializer expressions do not see
151              * our implementation classes. This should be almost as good as
152              * that, as we are resetting the fields to final before ever leaking
153              * the class.
154              */
155             for (final StaticBindingProperty constant : source.getStaticConstants()) {
156                 final Field field = cls.getDeclaredField(constant.getName());
157                 field.setAccessible(true);
158                 field.set(null, constant.getValue());
159
160                 if (FIELD_MODIFIERS != null) {
161                     FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
162                 }
163             }
164
165             return cls;
166         }
167     }
168
169     private AbstractTreeNodeSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
170         Types.typeForClass(type);
171         javassist.appendClassLoaderIfMissing(type.getClassLoader());
172         final Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
173         final GeneratedType generatedType = typeWithSchema.getKey();
174         final Object schema = typeWithSchema.getValue();
175
176         final AbstractTreeNodeSerializerSource source;
177         if (schema instanceof ContainerSchemaNode) {
178             source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
179         } else if (schema instanceof ListSchemaNode) {
180             final ListSchemaNode casted = (ListSchemaNode) schema;
181             if (casted.getKeyDefinition().isEmpty()) {
182                 source = generateUnkeyedListEntrySerializer(generatedType, casted);
183             } else {
184                 source = generateMapEntrySerializer(generatedType, casted);
185             }
186         } else if (schema instanceof AugmentationSchema) {
187             source = generateSerializer(generatedType, (AugmentationSchema) schema);
188         } else if (schema instanceof ChoiceCaseNode) {
189             source = generateCaseSerializer(generatedType, (ChoiceCaseNode) schema);
190         } else if (schema instanceof NotificationDefinition) {
191             source = generateNotificationSerializer(generatedType, (NotificationDefinition) schema);
192         } else {
193             throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
194         }
195         return source;
196     }
197
198     private CtClass generateEmitter0(final Class<?> type, final AbstractTreeNodeSerializerSource source,
199             final String serializerName) {
200         final CtClass product;
201
202         /*
203          * getSerializerBody() has side effects, such as loading classes and
204          * codecs, it should be run in model class loader in order to correctly
205          * reference load child classes.
206          *
207          * Furthermore the fact that getSerializedBody() can trigger other code
208          * generation to happen, we need to take care of this before calling
209          * instantiatePrototype(), as that will call our customizer with the
210          * lock held, hence any code generation will end up being blocked on the
211          * javassist lock.
212          */
213         final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(),
214                 (Supplier<String>) () -> source.getSerializerBody().toString());
215
216         try {
217             product = javassist.instantiatePrototype(TreeNodeSerializerPrototype.class.getName(), serializerName,
218                     cls -> {
219                         // Generate any static fields
220                         for (final StaticBindingProperty def : source.getStaticConstants()) {
221                             final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
222                             field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
223                             cls.addField(field);
224                         }
225
226                         // Replace serialize() -- may reference static fields
227                         final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
228                         serializeTo.setBody(body);
229
230                         // The prototype is not visible, so we need to take care
231                         // of that
232                         cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
233                     });
234         } catch (final NotFoundException e) {
235             LOG.error("Failed to instatiate serializer {}", source, e);
236             throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
237         }
238         return product;
239     }
240
241     /**
242      * Generates serializer source code for supplied container node, which will
243      * read supplied binding type and invoke proper methods on supplied
244      * {@link BindingStreamEventWriter}.
245      * <p>
246      * Implementation is required to recursively invoke events for all reachable
247      * binding objects.
248      *
249      * @param type - binding type of container
250      * @param node - schema of container
251      * @return source for container node writer
252      */
253     protected abstract AbstractTreeNodeSerializerSource generateContainerSerializer(GeneratedType type,
254             ContainerSchemaNode node);
255
256     /**
257      * Generates serializer source for supplied case node, which will read
258      * supplied binding type and invoke proper methods on supplied
259      * {@link BindingStreamEventWriter}.
260      * <p>
261      * Implementation is required to recursively invoke events for all reachable
262      * binding objects.
263      *
264      * @param type - binding type of case
265      * @param node - schema of case
266      * @return source for case node writer
267      */
268     protected abstract AbstractTreeNodeSerializerSource generateCaseSerializer(GeneratedType type, ChoiceCaseNode node);
269
270     /**
271      * Generates serializer source for supplied list node, which will read
272      * supplied binding type and invoke proper methods on supplied
273      * {@link BindingStreamEventWriter}.
274      * <p>
275      * Implementation is required to recursively invoke events for all reachable
276      * binding objects.
277      *
278      * @param type - binding type of list
279      * @param node - schema of list
280      * @return source for list node writer
281      */
282     protected abstract AbstractTreeNodeSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
283
284     /**
285      * Generates serializer source for supplied list node, which will read
286      * supplied binding type and invoke proper methods on supplied
287      * {@link BindingStreamEventWriter}.
288      * <p>
289      * Implementation is required to recursively invoke events for all reachable
290      * binding objects.
291      *
292      * @param type - binding type of list
293      * @param node - schema of list
294      * @return source for list node writer
295      */
296     protected abstract AbstractTreeNodeSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type,
297             ListSchemaNode node);
298
299     /**
300      * Generates serializer source for supplied augmentation node, which will
301      * read supplied binding type and invoke proper methods on supplied
302      * {@link BindingStreamEventWriter}.
303      * <p>
304      * Implementation is required to recursively invoke events for all reachable
305      * binding objects.
306      *
307      * @param type - binding type of augmentation
308      * @param schema - schema of augmentation
309      * @return source for augmentation node writer
310      */
311     protected abstract AbstractTreeNodeSerializerSource generateSerializer(GeneratedType type, AugmentationSchema schema);
312
313     /**
314      * Generates serializer source for notification node, which will read
315      * supplied binding type and invoke proper methods on supplied
316      * {@link BindingStreamEventWriter}.
317      * <p>
318      * Implementation is required to recursively invoke events for all reachable
319      * binding objects.
320      *
321      * @param type - binding type of notification
322      * @param node - schema of notification
323      * @return source for notification node writer
324      */
325     protected abstract AbstractTreeNodeSerializerSource generateNotificationSerializer(GeneratedType type,
326             NotificationDefinition node);
327
328 }