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.impl.util.BindingRuntimeContext;
32 import org.opendaylight.mdsal.binding.javav2.generator.impl.util.javassist.JavassistUtils;
33 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
34 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
35 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
36 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
37 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
38 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerImplementation;
39 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerRegistry;
40 import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections;
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 protected 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 f = Field.class.getDeclaredField("modifiers");
74 f.setAccessible(true);
75 } catch (NoSuchFieldException | SecurityException e) {
76 LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
82 protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
84 this.javassist = Preconditions.checkNotNull(utils, "JavassistUtils instance is required.");
85 this.serializeArguments = new CtClass[] { javassist.asCtClass(TreeNodeSerializerRegistry.class),
86 javassist.asCtClass(TreeNode.class), javassist.asCtClass(BindingStreamEventWriter.class), };
87 javassist.appendClassLoaderIfMissing(TreeNodeSerializerPrototype.class.getClassLoader());
88 this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
92 public final TreeNodeSerializerImplementation getSerializer(final Class<?> type) {
93 return implementations.getUnchecked(type);
97 public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
98 this.context = runtime;
102 public final String loadSerializerFor(final Class<?> cls) {
103 return implementations.getUnchecked(cls).getClass().getName();
106 private final class SerializerImplementationLoader extends CacheLoader<Class<?>, TreeNodeSerializerImplementation> {
108 private static final String GETINSTANCE_METHOD_NAME = "getInstance";
109 private static final String SERIALIZER_SUFFIX = "$StreamWriter";
111 private String getSerializerName(final Class<?> type) {
112 return type.getName() + SERIALIZER_SUFFIX;
116 @SuppressWarnings("unchecked")
117 public TreeNodeSerializerImplementation load(@Nonnull final Class<?> type) throws Exception {
118 Preconditions.checkArgument(BindingReflections.isBindingClass(type));
119 Preconditions.checkArgument(Instantiable.class.isAssignableFrom(type),
120 "Instantiable is not assingnable from %s from classloader %s.", type, type.getClassLoader());
122 final String serializerName = getSerializerName(type);
124 Class<? extends TreeNodeSerializerImplementation> cls;
126 cls = (Class<? extends TreeNodeSerializerImplementation>) ClassLoaderUtils
127 .loadClass(type.getClassLoader(), serializerName);
128 } catch (final ClassNotFoundException e) {
129 cls = generateSerializer(type, serializerName);
132 final TreeNodeSerializerImplementation obj =
133 (TreeNodeSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
134 LOG.debug("Loaded serializer {} for class {}", obj, type);
138 @SuppressWarnings("unchecked")
139 private Class<? extends TreeNodeSerializerImplementation> generateSerializer(final Class<?> type,
140 final String serializerName)
141 throws CannotCompileException, IllegalAccessException, IllegalArgumentException,
142 InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
143 final AbstractTreeNodeSerializerSource source = generateEmitterSource(type, serializerName);
144 final CtClass poolClass = generateEmitter0(type, source, serializerName);
145 final Class<? extends TreeNodeSerializerImplementation> cls =
146 poolClass.toClass(type.getClassLoader(), type.getProtectionDomain());
149 * Due to OSGi class loader rules we cannot initialize the fields
150 * during construction, as the initializer expressions do not see
151 * our implementation classes. This should be almost as good as
152 * that, as we are resetting the fields to final before ever leaking
155 for (final StaticBindingProperty constant : source.getStaticConstants()) {
156 final Field field = cls.getDeclaredField(constant.getName());
157 field.setAccessible(true);
158 field.set(null, constant.getValue());
160 if (FIELD_MODIFIERS != null) {
161 FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
169 private AbstractTreeNodeSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
170 Types.typeForClass(type);
171 javassist.appendClassLoaderIfMissing(type.getClassLoader());
172 final Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
173 final GeneratedType generatedType = typeWithSchema.getKey();
174 final Object schema = typeWithSchema.getValue();
176 final AbstractTreeNodeSerializerSource source;
177 if (schema instanceof ContainerSchemaNode) {
178 source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
179 } else if (schema instanceof ListSchemaNode) {
180 final ListSchemaNode casted = (ListSchemaNode) schema;
181 if (casted.getKeyDefinition().isEmpty()) {
182 source = generateUnkeyedListEntrySerializer(generatedType, casted);
184 source = generateMapEntrySerializer(generatedType, casted);
186 } else if (schema instanceof AugmentationSchema) {
187 source = generateSerializer(generatedType, (AugmentationSchema) schema);
188 } else if (schema instanceof ChoiceCaseNode) {
189 source = generateCaseSerializer(generatedType, (ChoiceCaseNode) schema);
190 } else if (schema instanceof NotificationDefinition) {
191 source = generateNotificationSerializer(generatedType, (NotificationDefinition) schema);
193 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
198 private CtClass generateEmitter0(final Class<?> type, final AbstractTreeNodeSerializerSource source,
199 final String serializerName) {
200 final CtClass product;
203 * getSerializerBody() has side effects, such as loading classes and
204 * codecs, it should be run in model class loader in order to correctly
205 * reference load child classes.
207 * Furthermore the fact that getSerializedBody() can trigger other code
208 * generation to happen, we need to take care of this before calling
209 * instantiatePrototype(), as that will call our customizer with the
210 * lock held, hence any code generation will end up being blocked on the
213 final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(),
214 (Supplier<String>) () -> source.getSerializerBody().toString());
217 product = javassist.instantiatePrototype(TreeNodeSerializerPrototype.class.getName(), serializerName,
219 // Generate any static fields
220 for (final StaticBindingProperty def : source.getStaticConstants()) {
221 final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
222 field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
226 // Replace serialize() -- may reference static fields
227 final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
228 serializeTo.setBody(body);
230 // The prototype is not visible, so we need to take care
232 cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
234 } catch (final NotFoundException e) {
235 LOG.error("Failed to instatiate serializer {}", source, e);
236 throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
242 * Generates serializer source code for supplied container node, which will
243 * read supplied binding type and invoke proper methods on supplied
244 * {@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}.
261 * Implementation is required to recursively invoke events for all reachable
264 * @param type - binding type of case
265 * @param node - schema of case
266 * @return source for case node writer
268 protected abstract AbstractTreeNodeSerializerSource generateCaseSerializer(GeneratedType type, ChoiceCaseNode node);
271 * Generates serializer source for supplied list node, which will read
272 * supplied binding type and invoke proper methods on supplied
273 * {@link BindingStreamEventWriter}.
275 * Implementation is required to recursively invoke events for all reachable
278 * @param type - binding type of list
279 * @param node - schema of list
280 * @return source for list node writer
282 protected abstract AbstractTreeNodeSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
285 * Generates serializer source for supplied list node, which will read
286 * supplied binding type and invoke proper methods on supplied
287 * {@link BindingStreamEventWriter}.
289 * Implementation is required to recursively invoke events for all reachable
292 * @param type - binding type of list
293 * @param node - schema of list
294 * @return source for list node writer
296 protected abstract AbstractTreeNodeSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type,
297 ListSchemaNode node);
300 * Generates serializer source for supplied augmentation node, which will
301 * read supplied binding type and invoke proper methods on supplied
302 * {@link BindingStreamEventWriter}.
304 * Implementation is required to recursively invoke events for all reachable
307 * @param type - binding type of augmentation
308 * @param schema - schema of augmentation
309 * @return source for augmentation node writer
311 protected abstract AbstractTreeNodeSerializerSource generateSerializer(GeneratedType type, AugmentationSchema schema);
314 * Generates serializer source for notification node, which will read
315 * supplied binding type and invoke proper methods on supplied
316 * {@link BindingStreamEventWriter}.
318 * Implementation is required to recursively invoke events for all reachable
321 * @param type - binding type of notification
322 * @param node - schema of notification
323 * @return source for notification node writer
325 protected abstract AbstractTreeNodeSerializerSource generateNotificationSerializer(GeneratedType type,
326 NotificationDefinition node);