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