2 * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.javav2.dom.codec.generator.spi.generator;
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.Collection;
19 import java.util.Map.Entry;
20 import javassist.CannotCompileException;
21 import javassist.CtClass;
22 import javassist.CtField;
23 import javassist.CtMethod;
24 import javassist.Modifier;
25 import javassist.NotFoundException;
26 import javax.annotation.Nonnull;
27 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.api.TreeNodeSerializerGenerator;
28 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.StaticBindingProperty;
29 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.TreeNodeSerializerPrototype;
30 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.spi.source.AbstractTreeNodeSerializerSource;
31 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.serializer.AugmentableDispatchSerializer;
32 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
33 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
34 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
35 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
36 import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
37 import org.opendaylight.mdsal.binding.javav2.runtime.javassist.JavassistUtils;
38 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
39 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
40 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
41 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
42 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerImplementation;
43 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerRegistry;
44 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
45 import org.opendaylight.yangtools.util.ClassLoaderUtils;
46 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 public abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements TreeNodeSerializerGenerator {
57 private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
59 public static final String SERIALIZE_METHOD_NAME = "serialize";
60 public static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
61 private static final Field FIELD_MODIFIERS;
63 private final LoadingCache<Class<?>, TreeNodeSerializerImplementation> implementations;
64 private final CtClass[] serializeArguments;
65 private final JavassistUtils javassist;
67 private BindingRuntimeContext context;
71 * Cache reflection access to field modifiers field. We need this to set
72 * fix the static declared fields to final once we initialize them. If
73 * we cannot get access, that's fine, too.
77 field = Field.class.getDeclaredField("modifiers");
78 field.setAccessible(true);
79 } catch (NoSuchFieldException | SecurityException e) {
80 LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
83 FIELD_MODIFIERS = field;
86 protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
87 this.javassist = Preconditions.checkNotNull(utils, "JavassistUtils instance is required.");
88 this.serializeArguments = new CtClass[] { javassist.asCtClass(TreeNodeSerializerRegistry.class),
89 javassist.asCtClass(TreeNode.class), javassist.asCtClass(BindingStreamEventWriter.class), };
90 javassist.appendClassLoaderIfMissing(TreeNodeSerializerPrototype.class.getClassLoader());
91 this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
95 public final TreeNodeSerializerImplementation getSerializer(final Class<?> type) {
96 return implementations.getUnchecked(type);
100 public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
101 this.context = runtime;
105 public final String loadSerializerFor(final Class<?> cls) {
106 return implementations.getUnchecked(cls).getClass().getName();
109 private final class SerializerImplementationLoader extends CacheLoader<Class<?>, TreeNodeSerializerImplementation> {
111 private static final String GETINSTANCE_METHOD_NAME = "getInstance";
112 private static final String SERIALIZER_SUFFIX = "$StreamWriter";
114 private String getSerializerName(final Class<?> type) {
115 return type.getName() + SERIALIZER_SUFFIX;
119 @SuppressWarnings("unchecked")
120 public TreeNodeSerializerImplementation load(@Nonnull final Class<?> type) throws Exception {
121 Preconditions.checkArgument(BindingReflections.isBindingClass(type));
122 Preconditions.checkArgument(Instantiable.class.isAssignableFrom(type),
123 "Instantiable is not assingnable from %s from classloader %s.", type, type.getClassLoader());
125 final String serializerName = getSerializerName(type);
127 Class<? extends TreeNodeSerializerImplementation> cls;
129 cls = (Class<? extends TreeNodeSerializerImplementation>) ClassLoaderUtils
130 .loadClass(type.getClassLoader(), serializerName);
131 } catch (final ClassNotFoundException e) {
132 cls = generateSerializer(type, serializerName);
135 final TreeNodeSerializerImplementation obj =
136 (TreeNodeSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
137 LOG.debug("Loaded serializer {} for class {}", obj, type);
141 @SuppressWarnings("unchecked")
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());
152 * Due to OSGi class loader rules we cannot initialize the fields
153 * during construction, as the initializer expressions do not see
154 * our implementation classes. This should be almost as good as
155 * that, as we are resetting the fields to final before ever leaking
158 for (final StaticBindingProperty constant : source.getStaticConstants()) {
159 final Field field = cls.getDeclaredField(constant.getName());
160 field.setAccessible(true);
161 field.set(null, constant.getValue());
163 if (FIELD_MODIFIERS != null) {
164 FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
172 private AbstractTreeNodeSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
173 Types.typeForClass(type);
174 javassist.appendClassLoaderIfMissing(type.getClassLoader());
176 if (Augmentation.class.isAssignableFrom(type)) {
177 final Entry<Type, Collection<AugmentationSchemaNode>> entry = context.getAugmentationDefinition(type);
178 return generateAugmentSerializer(((GeneratedTypeBuilder) entry.getKey()).toInstance(), entry.getValue());
181 final Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
182 final GeneratedType generatedType = typeWithSchema.getKey();
183 final Object schema = typeWithSchema.getValue();
185 final AbstractTreeNodeSerializerSource source;
186 if (schema instanceof ContainerSchemaNode) {
187 source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
188 } else if (schema instanceof ListSchemaNode) {
189 final ListSchemaNode casted = (ListSchemaNode) schema;
190 if (casted.getKeyDefinition().isEmpty()) {
191 source = generateUnkeyedListEntrySerializer(generatedType, casted);
193 source = generateMapEntrySerializer(generatedType, casted);
195 } else if (schema instanceof CaseSchemaNode) {
196 source = generateCaseSerializer(generatedType, (CaseSchemaNode) schema);
197 } else if (schema instanceof NotificationDefinition) {
198 source = generateNotificationSerializer(generatedType, (NotificationDefinition) schema);
200 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
205 private CtClass generateEmitter0(final Class<?> type, final AbstractTreeNodeSerializerSource source,
206 final String serializerName) {
207 final CtClass product;
210 * getSerializerBody() has side effects, such as loading classes and
211 * codecs, it should be run in model class loader in order to correctly
212 * reference load child classes.
214 * Furthermore the fact that getSerializedBody() can trigger other code
215 * generation to happen, we need to take care of this before calling
216 * instantiatePrototype(), as that will call our customizer with the
217 * lock held, hence any code generation will end up being blocked on the
220 final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(),
221 (Supplier<String>) () -> source.getSerializerBody().toString());
224 product = javassist.instantiatePrototype(TreeNodeSerializerPrototype.class.getName(), serializerName,
226 // Generate any static fields
227 for (final StaticBindingProperty def : source.getStaticConstants()) {
228 final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
229 field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
233 // Replace serialize() -- may reference static fields
234 final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
235 serializeTo.setBody(body);
237 // The prototype is not visible, so we need to take care
239 cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
241 } catch (final NotFoundException e) {
242 LOG.error("Failed to instatiate serializer {}", source, e);
243 throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
249 * Generates serializer source code for supplied container node, which will
250 * read supplied binding type and invoke proper methods on supplied
251 * {@link BindingStreamEventWriter}.
254 * Implementation is required to recursively invoke events for all reachable
257 * @param type - binding type of container
258 * @param node - schema of container
259 * @return source for container node writer
261 protected abstract AbstractTreeNodeSerializerSource generateContainerSerializer(GeneratedType type,
262 ContainerSchemaNode node);
265 * Generates serializer source for supplied case node, which will read
266 * supplied binding type and invoke proper methods on supplied
267 * {@link BindingStreamEventWriter}.
270 * Implementation is required to recursively invoke events for all reachable
273 * @param type - binding type of case
274 * @param node - schema of case
275 * @return source for case node writer
277 protected abstract AbstractTreeNodeSerializerSource generateCaseSerializer(GeneratedType type, CaseSchemaNode node);
280 * Generates serializer source for supplied list node, which will read
281 * supplied binding type and invoke proper methods on supplied
282 * {@link BindingStreamEventWriter}.
285 * Implementation is required to recursively invoke events for all reachable
288 * @param type - binding type of list
289 * @param node - schema of list
290 * @return source for list node writer
292 protected abstract AbstractTreeNodeSerializerSource generateMapEntrySerializer(GeneratedType type,
293 ListSchemaNode node);
296 * Generates serializer source for supplied list node, which will read
297 * supplied binding type and invoke proper methods on supplied
298 * {@link BindingStreamEventWriter}.
301 * Implementation is required to recursively invoke events for all reachable
304 * @param type - binding type of list
305 * @param node - schema of list
306 * @return source for list node writer
308 protected abstract AbstractTreeNodeSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type,
309 ListSchemaNode node);
312 * Generates serializer source for supplied augmentation node, which will
313 * read supplied binding type and invoke proper methods on supplied
314 * {@link BindingStreamEventWriter}.
317 * Implementation is required to recursively invoke events for all reachable
320 * @param type - binding type of augmentation
321 * @param schemas - schemas of augmentation
322 * @return source for augmentation node writer
324 protected abstract AbstractTreeNodeSerializerSource generateAugmentSerializer(GeneratedType type,
325 Collection<AugmentationSchemaNode> schemas);
328 * Generates serializer source for notification node, which will read
329 * supplied binding type and invoke proper methods on supplied
330 * {@link BindingStreamEventWriter}.
333 * Implementation is required to recursively invoke events for all reachable
336 * @param type - binding type of notification
337 * @param node - schema of notification
338 * @return source for notification node writer
340 protected abstract AbstractTreeNodeSerializerSource generateNotificationSerializer(GeneratedType type,
341 NotificationDefinition node);