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