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.Map.Entry;
19 import javassist.CannotCompileException;
20 import javassist.CtClass;
21 import javassist.CtField;
22 import javassist.CtMethod;
23 import javassist.Modifier;
24 import javassist.NotFoundException;
25 import javax.annotation.Nonnull;
26 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.api.TreeNodeSerializerGenerator;
27 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.StaticBindingProperty;
28 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.impl.TreeNodeSerializerPrototype;
29 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.spi.source.AbstractTreeNodeSerializerSource;
30 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.serializer.AugmentableDispatchSerializer;
31 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
32 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
33 import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
34 import org.opendaylight.mdsal.binding.javav2.runtime.javassist.JavassistUtils;
35 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
36 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
37 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
38 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
39 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerImplementation;
40 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerRegistry;
41 import org.opendaylight.yangtools.util.ClassLoaderUtils;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
43 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
44 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 public abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements TreeNodeSerializerGenerator {
53 private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
55 public static final String SERIALIZE_METHOD_NAME = "serialize";
56 public static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
57 private static final Field FIELD_MODIFIERS;
59 private final LoadingCache<Class<?>, TreeNodeSerializerImplementation> implementations;
60 private final CtClass[] serializeArguments;
61 private final JavassistUtils javassist;
63 private BindingRuntimeContext context;
67 * Cache reflection access to field modifiers field. We need this to set
68 * fix the static declared fields to final once we initialize them. If
69 * we cannot get access, that's fine, too.
73 field = Field.class.getDeclaredField("modifiers");
74 field.setAccessible(true);
75 } catch (NoSuchFieldException | SecurityException e) {
76 LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
79 FIELD_MODIFIERS = field;
82 protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
83 this.javassist = Preconditions.checkNotNull(utils, "JavassistUtils instance is required.");
84 this.serializeArguments = new CtClass[] { javassist.asCtClass(TreeNodeSerializerRegistry.class),
85 javassist.asCtClass(TreeNode.class), javassist.asCtClass(BindingStreamEventWriter.class), };
86 javassist.appendClassLoaderIfMissing(TreeNodeSerializerPrototype.class.getClassLoader());
87 this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
91 public final TreeNodeSerializerImplementation getSerializer(final Class<?> type) {
92 return implementations.getUnchecked(type);
96 public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
97 this.context = runtime;
101 public final String loadSerializerFor(final Class<?> cls) {
102 return implementations.getUnchecked(cls).getClass().getName();
105 private final class SerializerImplementationLoader extends CacheLoader<Class<?>, TreeNodeSerializerImplementation> {
107 private static final String GETINSTANCE_METHOD_NAME = "getInstance";
108 private static final String SERIALIZER_SUFFIX = "$StreamWriter";
110 private String getSerializerName(final Class<?> type) {
111 return type.getName() + SERIALIZER_SUFFIX;
115 @SuppressWarnings("unchecked")
116 public TreeNodeSerializerImplementation load(@Nonnull final Class<?> type) throws Exception {
117 Preconditions.checkArgument(BindingReflections.isBindingClass(type));
118 Preconditions.checkArgument(Instantiable.class.isAssignableFrom(type),
119 "Instantiable is not assingnable from %s from classloader %s.", type, type.getClassLoader());
121 final String serializerName = getSerializerName(type);
123 Class<? extends TreeNodeSerializerImplementation> cls;
125 cls = (Class<? extends TreeNodeSerializerImplementation>) ClassLoaderUtils
126 .loadClass(type.getClassLoader(), serializerName);
127 } catch (final ClassNotFoundException e) {
128 cls = generateSerializer(type, serializerName);
131 final TreeNodeSerializerImplementation obj =
132 (TreeNodeSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
133 LOG.debug("Loaded serializer {} for class {}", obj, type);
137 @SuppressWarnings("unchecked")
138 private Class<? extends TreeNodeSerializerImplementation> generateSerializer(final Class<?> type,
139 final String serializerName)
140 throws CannotCompileException, IllegalAccessException, IllegalArgumentException,
141 InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
142 final AbstractTreeNodeSerializerSource source = generateEmitterSource(type, serializerName);
143 final CtClass poolClass = generateEmitter0(type, source, serializerName);
144 final Class<? extends TreeNodeSerializerImplementation> cls =
145 poolClass.toClass(type.getClassLoader(), type.getProtectionDomain());
148 * Due to OSGi class loader rules we cannot initialize the fields
149 * during construction, as the initializer expressions do not see
150 * our implementation classes. This should be almost as good as
151 * that, as we are resetting the fields to final before ever leaking
154 for (final StaticBindingProperty constant : source.getStaticConstants()) {
155 final Field field = cls.getDeclaredField(constant.getName());
156 field.setAccessible(true);
157 field.set(null, constant.getValue());
159 if (FIELD_MODIFIERS != null) {
160 FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
168 private AbstractTreeNodeSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
169 Types.typeForClass(type);
170 javassist.appendClassLoaderIfMissing(type.getClassLoader());
171 final Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
172 final GeneratedType generatedType = typeWithSchema.getKey();
173 final Object schema = typeWithSchema.getValue();
175 final AbstractTreeNodeSerializerSource source;
176 if (schema instanceof ContainerSchemaNode) {
177 source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
178 } else if (schema instanceof ListSchemaNode) {
179 final ListSchemaNode casted = (ListSchemaNode) schema;
180 if (casted.getKeyDefinition().isEmpty()) {
181 source = generateUnkeyedListEntrySerializer(generatedType, casted);
183 source = generateMapEntrySerializer(generatedType, casted);
185 } else if (schema instanceof AugmentationSchema) {
186 source = generateSerializer(generatedType, (AugmentationSchema) schema);
187 } else if (schema instanceof ChoiceCaseNode) {
188 source = generateCaseSerializer(generatedType, (ChoiceCaseNode) schema);
189 } else if (schema instanceof NotificationDefinition) {
190 source = generateNotificationSerializer(generatedType, (NotificationDefinition) schema);
192 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
197 private CtClass generateEmitter0(final Class<?> type, final AbstractTreeNodeSerializerSource source,
198 final String serializerName) {
199 final CtClass product;
202 * getSerializerBody() has side effects, such as loading classes and
203 * codecs, it should be run in model class loader in order to correctly
204 * reference load child classes.
206 * Furthermore the fact that getSerializedBody() can trigger other code
207 * generation to happen, we need to take care of this before calling
208 * instantiatePrototype(), as that will call our customizer with the
209 * lock held, hence any code generation will end up being blocked on the
212 final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(),
213 (Supplier<String>) () -> source.getSerializerBody().toString());
216 product = javassist.instantiatePrototype(TreeNodeSerializerPrototype.class.getName(), serializerName,
218 // Generate any static fields
219 for (final StaticBindingProperty def : source.getStaticConstants()) {
220 final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
221 field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
225 // Replace serialize() -- may reference static fields
226 final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
227 serializeTo.setBody(body);
229 // The prototype is not visible, so we need to take care
231 cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
233 } catch (final NotFoundException e) {
234 LOG.error("Failed to instatiate serializer {}", source, e);
235 throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
241 * Generates serializer source code for supplied container node, which will
242 * read supplied binding type and invoke proper methods on supplied
243 * {@link BindingStreamEventWriter}.
246 * Implementation is required to recursively invoke events for all reachable
249 * @param type - binding type of container
250 * @param node - schema of container
251 * @return source for container node writer
253 protected abstract AbstractTreeNodeSerializerSource generateContainerSerializer(GeneratedType type,
254 ContainerSchemaNode node);
257 * Generates serializer source for supplied case node, which will read
258 * supplied binding type and invoke proper methods on supplied
259 * {@link BindingStreamEventWriter}.
262 * Implementation is required to recursively invoke events for all reachable
265 * @param type - binding type of case
266 * @param node - schema of case
267 * @return source for case node writer
269 protected abstract AbstractTreeNodeSerializerSource generateCaseSerializer(GeneratedType type, ChoiceCaseNode node);
272 * Generates serializer source for supplied list node, which will read
273 * supplied binding type and invoke proper methods on supplied
274 * {@link BindingStreamEventWriter}.
277 * Implementation is required to recursively invoke events for all reachable
280 * @param type - binding type of list
281 * @param node - schema of list
282 * @return source for list node writer
284 protected abstract AbstractTreeNodeSerializerSource generateMapEntrySerializer(GeneratedType type,
285 ListSchemaNode node);
288 * Generates serializer source for supplied list node, which will read
289 * supplied binding type and invoke proper methods on supplied
290 * {@link BindingStreamEventWriter}.
293 * Implementation is required to recursively invoke events for all reachable
296 * @param type - binding type of list
297 * @param node - schema of list
298 * @return source for list node writer
300 protected abstract AbstractTreeNodeSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type,
301 ListSchemaNode node);
304 * Generates serializer source for supplied augmentation node, which will
305 * read supplied binding type and invoke proper methods on supplied
306 * {@link BindingStreamEventWriter}.
309 * Implementation is required to recursively invoke events for all reachable
312 * @param type - binding type of augmentation
313 * @param schema - schema of augmentation
314 * @return source for augmentation node writer
316 protected abstract AbstractTreeNodeSerializerSource generateSerializer(GeneratedType type,
317 AugmentationSchema schema);
320 * Generates serializer source for notification node, which will read
321 * supplied binding type and invoke proper methods on supplied
322 * {@link BindingStreamEventWriter}.
325 * Implementation is required to recursively invoke events for all reachable
328 * @param type - binding type of notification
329 * @param node - schema of notification
330 * @return source for notification node writer
332 protected abstract AbstractTreeNodeSerializerSource generateNotificationSerializer(GeneratedType type,
333 NotificationDefinition node);