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.yangtools.binding.data.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;
16 import java.lang.reflect.Field;
17 import java.lang.reflect.InvocationTargetException;
18 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;
27 import org.opendaylight.yangtools.binding.data.codec.gen.spi.StaticConstantDefinition;
28 import org.opendaylight.yangtools.binding.data.codec.util.AugmentableDispatchSerializer;
29 import org.opendaylight.yangtools.binding.generator.util.Types;
30 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
31 import org.opendaylight.yangtools.sal.binding.generator.util.ClassCustomizer;
32 import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
33 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType;
34 import org.opendaylight.yangtools.util.ClassLoaderUtils;
35 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
36 import org.opendaylight.yangtools.yang.binding.DataContainer;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
39 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
40 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
41 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
42 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements DataObjectSerializerGenerator {
49 private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
51 protected static final String SERIALIZE_METHOD_NAME = "serialize";
52 protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
53 private static final Field FIELD_MODIFIERS;
55 private final LoadingCache<Class<?>, DataObjectSerializerImplementation> implementations;
56 private final CtClass[] serializeArguments;
57 private final JavassistUtils javassist;
58 private BindingRuntimeContext context;
62 * Cache reflection access to field modifiers field. We need this to set
63 * fix the static declared fields to final once we initialize them. If we
64 * cannot get access, that's fine, too.
68 f = Field.class.getDeclaredField("modifiers");
69 f.setAccessible(true);
70 } catch (NoSuchFieldException | SecurityException e) {
71 LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
77 protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
79 this.javassist = Preconditions.checkNotNull(utils,"JavassistUtils instance is required.");
80 this.serializeArguments = new CtClass[] {
81 javassist.asCtClass(DataObjectSerializerRegistry.class),
82 javassist.asCtClass(DataObject.class),
83 javassist.asCtClass(BindingStreamEventWriter.class),
85 javassist.appendClassLoaderIfMissing(DataObjectSerializerPrototype.class.getClassLoader());
86 this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
90 public final DataObjectSerializerImplementation getSerializer(final Class<?> type) {
91 return implementations.getUnchecked(type);
95 public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
96 this.context = runtime;
100 protected final String loadSerializerFor(final Class<?> cls) {
101 return implementations.getUnchecked(cls).getClass().getName();
104 private final class SerializerImplementationLoader extends CacheLoader<Class<?>, DataObjectSerializerImplementation> {
106 private static final String GETINSTANCE_METHOD_NAME = "getInstance";
107 private static final String SERIALIZER_SUFFIX = "$StreamWriter";
109 private String getSerializerName(final Class<?> type) {
110 return type.getName() + SERIALIZER_SUFFIX;
114 @SuppressWarnings("unchecked")
115 public DataObjectSerializerImplementation load(final Class<?> type) throws Exception {
116 Preconditions.checkArgument(BindingReflections.isBindingClass(type));
117 Preconditions.checkArgument(DataContainer.class.isAssignableFrom(type),"DataContainer is not assingnable from %s from classloader %s.",type,type.getClassLoader());
119 final String serializerName = getSerializerName(type);
121 Class<? extends DataObjectSerializerImplementation> cls;
123 cls = (Class<? extends DataObjectSerializerImplementation>) ClassLoaderUtils
124 .loadClass(type.getClassLoader(), serializerName);
125 } catch (ClassNotFoundException e) {
126 cls = generateSerializer(type, serializerName);
129 final DataObjectSerializerImplementation obj =
130 (DataObjectSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
131 LOG.debug("Loaded serializer {} for class {}", obj, type);
135 private Class<? extends DataObjectSerializerImplementation> generateSerializer(final Class<?> type,
136 final String serializerName) throws CannotCompileException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
137 final DataObjectSerializerSource source = generateEmitterSource(type, serializerName);
138 final CtClass poolClass = generateEmitter0(type, source, serializerName);
139 @SuppressWarnings("unchecked")
140 final Class<? extends DataObjectSerializerImplementation> cls = poolClass.toClass(type.getClassLoader(), type.getProtectionDomain());
143 * Due to OSGi class loader rules we cannot initialize the fields during
144 * construction, as the initializer expressions do not see our implementation
145 * classes. This should be almost as good as that, as we are resetting the
146 * fields to final before ever leaking the class.
148 for (StaticConstantDefinition constant : source.getStaticConstants()) {
149 final Field field = cls.getDeclaredField(constant.getName());
150 field.setAccessible(true);
151 field.set(null, constant.getValue());
153 if (FIELD_MODIFIERS != null) {
154 FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
162 private DataObjectSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
163 Types.typeForClass(type);
164 javassist.appendClassLoaderIfMissing(type.getClassLoader());
165 Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
166 GeneratedType generatedType = typeWithSchema.getKey();
167 Object schema = typeWithSchema.getValue();
169 final DataObjectSerializerSource source;
170 if (schema instanceof ContainerSchemaNode) {
171 source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
172 } else if (schema instanceof ListSchemaNode){
173 ListSchemaNode casted = (ListSchemaNode) schema;
174 if (casted.getKeyDefinition().isEmpty()) {
175 source = generateUnkeyedListEntrySerializer(generatedType, casted);
177 source = generateMapEntrySerializer(generatedType, casted);
179 } else if(schema instanceof AugmentationSchema) {
180 source = generateSerializer(generatedType,(AugmentationSchema) schema);
181 } else if(schema instanceof ChoiceCaseNode) {
182 source = generateCaseSerializer(generatedType,(ChoiceCaseNode) schema);
184 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
189 private CtClass generateEmitter0(final Class<?> type, final DataObjectSerializerSource source, final String serializerName) {
190 final CtClass product;
192 product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName, new ClassCustomizer() {
194 public void customizeClass(final CtClass cls) throws CannotCompileException, NotFoundException {
195 /* getSerializerBody() has side effects, such as loading classes
196 * and codecs, it should be run in model class loader in order to
197 * correctly reference load child classes
199 final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(), new Supplier<String>() {
201 public String get() {
202 return source.getSerializerBody().toString();
207 // Generate any static fields
208 for (StaticConstantDefinition def : source.getStaticConstants()) {
209 CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
210 field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
214 // Replace serialize() -- may reference static fields
215 final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
216 serializeTo.setBody(body);
218 // The prototype is not visible, so we need to take care of that
219 cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
222 } catch (NotFoundException e) {
223 LOG.error("Failed to instatiate serializer {}", source, e);
224 throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
230 * Generates serializer source code for supplied container node,
231 * which will read supplied binding type and invoke proper methods
232 * on supplied {@link BindingStreamEventWriter}.
234 * Implementation is required to recursively invoke events
235 * for all reachable binding objects.
237 * @param type Binding type of container
238 * @param node Schema of container
239 * @return Source for container node writer
241 protected abstract DataObjectSerializerSource generateContainerSerializer(GeneratedType type, ContainerSchemaNode node);
244 * Generates serializer source for supplied case node,
245 * which will read supplied binding type and invoke proper methods
246 * on supplied {@link BindingStreamEventWriter}.
248 * Implementation is required to recursively invoke events
249 * for all reachable binding objects.
251 * @param type Binding type of case
252 * @param node Schema of case
253 * @return Source for case node writer
255 protected abstract DataObjectSerializerSource generateCaseSerializer(GeneratedType type, ChoiceCaseNode node);
258 * Generates serializer source for supplied list node,
259 * which will read supplied binding type and invoke proper methods
260 * on supplied {@link BindingStreamEventWriter}.
262 * Implementation is required to recursively invoke events
263 * for all reachable binding objects.
265 * @param type Binding type of list
266 * @param node Schema of list
267 * @return Source for list node writer
269 protected abstract DataObjectSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
272 * Generates serializer source for supplied list node,
273 * which will read supplied binding type and invoke proper methods
274 * on supplied {@link BindingStreamEventWriter}.
276 * Implementation is required to recursively invoke events
277 * for all reachable binding objects.
279 * @param type Binding type of list
280 * @param node Schema of list
281 * @return Source for list node writer
283 protected abstract DataObjectSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type, ListSchemaNode node);
286 * Generates serializer source for supplied augmentation node,
287 * which will read supplied binding type and invoke proper methods
288 * on supplied {@link BindingStreamEventWriter}.
290 * Implementation is required to recursively invoke events
291 * for all reachable binding objects.
293 * @param type Binding type of augmentation
294 * @param node Schema of augmentation
295 * @return Source for augmentation node writer
297 protected abstract DataObjectSerializerSource generateSerializer(GeneratedType type, AugmentationSchema schema);