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