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