2 * Copyright (c) 2014 Cisco Systems, Inc. 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.dom.codec.gen.impl;
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.dom.codec.gen.spi.StaticConstantDefinition;
25 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentableDispatchSerializer;
26 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
27 import org.opendaylight.mdsal.binding.generator.util.JavassistUtils;
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
29 import org.opendaylight.mdsal.binding.model.util.Types;
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;
45 abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements DataObjectSerializerGenerator {
46 private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
48 protected static final String SERIALIZE_METHOD_NAME = "serialize";
49 protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
50 private static final Field FIELD_MODIFIERS;
52 private final LoadingCache<Class<?>, DataObjectSerializerImplementation> implementations;
53 private final CtClass[] serializeArguments;
54 private final JavassistUtils javassist;
55 private BindingRuntimeContext context;
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.
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);
74 protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
75 this.javassist = Preconditions.checkNotNull(utils,"JavassistUtils instance is required.");
76 this.serializeArguments = new CtClass[] {
77 javassist.asCtClass(DataObjectSerializerRegistry.class),
78 javassist.asCtClass(DataObject.class),
79 javassist.asCtClass(BindingStreamEventWriter.class),
81 javassist.appendClassLoaderIfMissing(DataObjectSerializerPrototype.class.getClassLoader());
82 this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
86 public final DataObjectSerializerImplementation getSerializer(final Class<?> type) {
87 return implementations.getUnchecked(type);
91 public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
92 this.context = runtime;
96 protected final String loadSerializerFor(final Class<?> cls) {
97 return implementations.getUnchecked(cls).getClass().getName();
100 private final class SerializerImplementationLoader extends CacheLoader<Class<?>, DataObjectSerializerImplementation> {
102 private static final String GETINSTANCE_METHOD_NAME = "getInstance";
103 private static final String SERIALIZER_SUFFIX = "$StreamWriter";
105 private String getSerializerName(final Class<?> type) {
106 return type.getName() + SERIALIZER_SUFFIX;
110 @SuppressWarnings("unchecked")
111 public DataObjectSerializerImplementation load(final Class<?> type) throws Exception {
112 Preconditions.checkArgument(BindingReflections.isBindingClass(type));
113 Preconditions.checkArgument(DataContainer.class.isAssignableFrom(type),"DataContainer is not assingnable from %s from classloader %s.",type,type.getClassLoader());
115 final String serializerName = getSerializerName(type);
117 Class<? extends DataObjectSerializerImplementation> cls;
119 cls = (Class<? extends DataObjectSerializerImplementation>) ClassLoaderUtils
120 .loadClass(type.getClassLoader(), serializerName);
121 } catch (final ClassNotFoundException e) {
122 cls = generateSerializer(type, serializerName);
125 final DataObjectSerializerImplementation obj =
126 (DataObjectSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
127 LOG.debug("Loaded serializer {} for class {}", obj, type);
131 private Class<? extends DataObjectSerializerImplementation> generateSerializer(final Class<?> type,
132 final String serializerName) throws CannotCompileException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
133 final DataObjectSerializerSource source = generateEmitterSource(type, serializerName);
134 final CtClass poolClass = generateEmitter0(type, source, serializerName);
135 @SuppressWarnings("unchecked")
136 final Class<? extends DataObjectSerializerImplementation> cls = poolClass.toClass(type.getClassLoader(), type.getProtectionDomain());
139 * Due to OSGi class loader rules we cannot initialize the fields during
140 * construction, as the initializer expressions do not see our implementation
141 * classes. This should be almost as good as that, as we are resetting the
142 * fields to final before ever leaking the class.
144 for (final StaticConstantDefinition constant : source.getStaticConstants()) {
145 final Field field = cls.getDeclaredField(constant.getName());
146 field.setAccessible(true);
147 field.set(null, constant.getValue());
149 if (FIELD_MODIFIERS != null) {
150 FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
158 private DataObjectSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
159 Types.typeForClass(type);
160 javassist.appendClassLoaderIfMissing(type.getClassLoader());
161 final Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
162 final GeneratedType generatedType = typeWithSchema.getKey();
163 final Object schema = typeWithSchema.getValue();
165 final DataObjectSerializerSource source;
166 if (schema instanceof ContainerSchemaNode) {
167 source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
168 } else if (schema instanceof ListSchemaNode){
169 final ListSchemaNode casted = (ListSchemaNode) schema;
170 if (casted.getKeyDefinition().isEmpty()) {
171 source = generateUnkeyedListEntrySerializer(generatedType, casted);
173 source = generateMapEntrySerializer(generatedType, casted);
175 } else if(schema instanceof AugmentationSchema) {
176 source = generateSerializer(generatedType,(AugmentationSchema) schema);
177 } else if(schema instanceof ChoiceCaseNode) {
178 source = generateCaseSerializer(generatedType,(ChoiceCaseNode) schema);
179 } else if(schema instanceof NotificationDefinition) {
180 source = generateNotificationSerializer(generatedType,(NotificationDefinition) schema);
182 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
187 private CtClass generateEmitter0(final Class<?> type, final DataObjectSerializerSource source, final String serializerName) {
188 final CtClass product;
191 * getSerializerBody() has side effects, such as loading classes and codecs, it should be run in model class
192 * loader in order to correctly reference load child classes.
194 * Furthermore the fact that getSerializedBody() can trigger other code generation to happen, we need to take
195 * care of this before calling instantiatePrototype(), as that will call our customizer with the lock held,
196 * hence any code generation will end up being blocked on the javassist lock.
198 final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(),
199 (Supplier<String>) () -> source.getSerializerBody().toString());
202 product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName, cls -> {
203 // Generate any static fields
204 for (final StaticConstantDefinition def : source.getStaticConstants()) {
205 final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
206 field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
210 // Replace serialize() -- may reference static fields
211 final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
212 serializeTo.setBody(body);
214 // The prototype is not visible, so we need to take care of that
215 cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
217 } catch (final NotFoundException e) {
218 LOG.error("Failed to instatiate serializer {}", source, e);
219 throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
225 * Generates serializer source code for supplied container node,
226 * which will read supplied binding type and invoke proper methods
227 * on supplied {@link BindingStreamEventWriter}.
229 * Implementation is required to recursively invoke events
230 * for all reachable binding objects.
232 * @param type Binding type of container
233 * @param node Schema of container
234 * @return Source for container node writer
236 protected abstract DataObjectSerializerSource generateContainerSerializer(GeneratedType type, ContainerSchemaNode node);
239 * Generates serializer source for supplied case node,
240 * which will read supplied binding type and invoke proper methods
241 * on supplied {@link BindingStreamEventWriter}.
243 * Implementation is required to recursively invoke events
244 * for all reachable binding objects.
246 * @param type Binding type of case
247 * @param node Schema of case
248 * @return Source for case node writer
250 protected abstract DataObjectSerializerSource generateCaseSerializer(GeneratedType type, ChoiceCaseNode node);
253 * Generates serializer source for supplied list node,
254 * which will read supplied binding type and invoke proper methods
255 * on supplied {@link BindingStreamEventWriter}.
257 * Implementation is required to recursively invoke events
258 * for all reachable binding objects.
260 * @param type Binding type of list
261 * @param node Schema of list
262 * @return Source for list node writer
264 protected abstract DataObjectSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
267 * Generates serializer source for supplied list node,
268 * which will read supplied binding type and invoke proper methods
269 * on supplied {@link BindingStreamEventWriter}.
271 * Implementation is required to recursively invoke events
272 * for all reachable binding objects.
274 * @param type Binding type of list
275 * @param node Schema of list
276 * @return Source for list node writer
278 protected abstract DataObjectSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type, ListSchemaNode node);
281 * Generates serializer source for supplied augmentation node,
282 * which will read supplied binding type and invoke proper methods
283 * on supplied {@link BindingStreamEventWriter}.
285 * Implementation is required to recursively invoke events
286 * for all reachable binding objects.
288 * @param type Binding type of augmentation
289 * @param schema Schema of augmentation
290 * @return Source for augmentation node writer
292 protected abstract DataObjectSerializerSource generateSerializer(GeneratedType type, AugmentationSchema schema);
295 * Generates serializer source for notification node,
296 * which will read supplied binding type and invoke proper methods
297 * on supplied {@link BindingStreamEventWriter}.
299 * Implementation is required to recursively invoke events
300 * for all reachable binding objects.
302 * @param type Binding type of notification
303 * @param node Schema of notification
304 * @return Source for notification node writer
306 protected abstract DataObjectSerializerSource generateNotificationSerializer(GeneratedType type, NotificationDefinition node);