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