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