BUG-1487: use prototypes for instantiating codecs 08/9708/8
authorRobert Varga <rovarga@cisco.com>
Tue, 5 Aug 2014 14:37:01 +0000 (16:37 +0200)
committerRobert Varga <rovarga@cisco.com>
Wed, 6 Aug 2014 11:30:43 +0000 (13:30 +0200)
Optimizes the per-class overhead by copying a prototype class, and then
setting the proper serializer name. At the same time we get rid of the
static serializer methods, making the interface a bit cleaner.
Additionally, we hide the AbstractStreamWriterGenerator, so that to have
a proper boundary.

Change-Id: Ia7d56ff16e1eec7204fa087c49f2ee55a658a377
Signed-off-by: Robert Varga <rovarga@cisco.com>
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/AbstractStreamWriterGenerator.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerGenerator.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerPrototype.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/StreamWriterGenerator.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/spi/AbstractSource.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCodecRegistry.java
code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassCustomizer.java [new file with mode: 0644]
code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/JavassistUtils.java

index be35585869b619fc9c72a000caae4d6e86d6475b..8fa66ce6b7baf11c9ebb3d2c626cb895467423d5 100644 (file)
@@ -29,7 +29,7 @@ import org.opendaylight.yangtools.binding.generator.util.Types;
 import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy;
 import org.opendaylight.yangtools.sal.binding.generator.impl.GeneratedClassLoadingStrategy;
 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
-import org.opendaylight.yangtools.sal.binding.generator.util.ClassGenerator;
+import org.opendaylight.yangtools.sal.binding.generator.util.ClassCustomizer;
 import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType;
 import org.opendaylight.yangtools.sal.binding.model.api.Type;
@@ -47,47 +47,43 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AbstractStreamWriterGenerator {
+abstract class AbstractStreamWriterGenerator implements DataObjectSerializerGenerator {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
+    private static final ClassLoadingStrategy STRATEGY = GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy();
+    private static final String SERIALIZER_SUFFIX = "$StreamWriter";
 
-    protected static final String SERIALIZER_SUFFIX = "$StreamWriter";
-    protected static final String STATIC_SERIALIZE_METHOD_NAME = "staticSerialize";
     protected static final String SERIALIZE_METHOD_NAME = "serialize";
-
     protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
-
-    private final JavassistUtils javassist;
-    private final CtClass serializerCt;
-    private final CtClass dataObjectCt;
-    private final CtClass writerCt;
-    private final CtClass voidCt;
-    private final CtClass registryCt;
-
-    private final CtClass[] serializeArguments;
-    private final CtMethod serializeToMethod;
 
     private final LoadingCache<Class<?>, Class<? extends DataObjectSerializerImplementation>> implementations;
-    private final ClassLoadingStrategy strategy;
-
+    private final CtClass[] serializeArguments;
+    private final JavassistUtils javassist;
     private BindingRuntimeContext context;
 
     protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
         super();
         this.javassist = Preconditions.checkNotNull(utils,"JavassistUtils instance is required.");
-        this.serializerCt = javassist.asCtClass(DataObjectSerializerImplementation.class);
-        this.registryCt = javassist.asCtClass(DataObjectSerializerRegistry.class);
-        this.writerCt = javassist.asCtClass(BindingStreamEventWriter.class);
-        this.dataObjectCt = javassist.asCtClass(DataObject.class);
-        this.voidCt = javassist.asCtClass(Void.class);
-        this.serializeArguments =  new CtClass[] { registryCt,dataObjectCt, writerCt };
+        this.serializeArguments = new CtClass[] {
+                javassist.asCtClass(DataObjectSerializerRegistry.class),
+                javassist.asCtClass(DataObject.class),
+                javassist.asCtClass(BindingStreamEventWriter.class),
+        };
 
+        this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
+    }
+
+    @Override
+    public final DataObjectSerializerImplementation getSerializer(final Class<?> type) {
         try {
-            this.serializeToMethod = serializerCt.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
-        } catch (NotFoundException e) {
-            throw new IllegalStateException("Required method " + SERIALIZE_METHOD_NAME + "was not found in class " + BindingStreamEventWriter.class ,e);
+            return implementations.getUnchecked(type).newInstance();
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new IllegalStateException(e);
         }
-        this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
-        strategy = GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy();
+    }
+
+    @Override
+    public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
+        this.context = runtime;
     }
 
     private final class SerializerImplementationLoader extends
@@ -132,8 +128,6 @@ public abstract class AbstractStreamWriterGenerator {
         }
     }
 
-
-
     protected DataObjectSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
         Types.typeForClass(type);
         Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
@@ -161,62 +155,42 @@ public abstract class AbstractStreamWriterGenerator {
     }
 
     private CtClass generateEmitter0(final DataObjectSerializerSource source, final String serializerName) {
-        CtClass product = javassist.createClass(serializerName, serializerCt, new ClassGenerator() {
-
-            @Override
-            public void process(final CtClass cls) {
-                final String staticBody = source.getStaticSerializeBody().toString();
-                try {
-
-                    for(StaticConstantDefinition def : source.getStaticConstants()) {
+        final CtClass product;
+        try {
+            product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName, new ClassCustomizer() {
+                @Override
+                public void customizeClass(final CtClass cls) throws CannotCompileException, NotFoundException {
+                    // getSerializerBody() has side effects
+                    final String body = source.getSerializerBody().toString();
+
+                    // Generate any static fields
+                    for (StaticConstantDefinition def : source.getStaticConstants()) {
                         CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
                         field.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
                         cls.addField(field);
                     }
 
-                    CtMethod staticSerializeTo = new CtMethod(voidCt, STATIC_SERIALIZE_METHOD_NAME, serializeArguments, cls);
-                    staticSerializeTo.setModifiers(Modifier.PUBLIC + Modifier.FINAL + Modifier.STATIC);
-                    staticSerializeTo.setBody(staticBody);
-                    cls.addMethod(staticSerializeTo);
-
-
-                    CtMethod serializeTo = new CtMethod(serializeToMethod,cls,null);
-                    serializeTo.setModifiers(Modifier.PUBLIC + Modifier.FINAL);
-                    serializeTo.setBody(
-                            new StringBuilder().append('{')
-                            .append(STATIC_SERIALIZE_METHOD_NAME).append("($$);\n")
-                            .append("return null;")
-                            .append('}')
-                            .toString()
-                            );
-                    cls.addMethod(serializeTo);
-                } catch (CannotCompileException e) {
-                    LOG.error("Can not compile body of codec for {}.",serializerName,e);
-                    throw new IllegalStateException(e);
+                    // Replace serialize() -- may reference static fields
+                    final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
+                    serializeTo.setBody(body);
                 }
-
-            }
-        });
+            });
+        } catch (NotFoundException e) {
+            LOG.error("Failed to instatiate serializer {}", source, e);
+            throw new LinkageError("Unexpected instantation problem: prototype not found", e);
+        }
         return product;
     }
 
     @SuppressWarnings("unchecked")
     protected Class<? extends DataContainer> loadClass(final Type childType) {
         try {
-            return (Class<? extends DataContainer>) strategy.loadClass(childType);
+            return (Class<? extends DataContainer>) STRATEGY.loadClass(childType);
         } catch (ClassNotFoundException e) {
             throw new IllegalStateException("Could not load referenced class ",e);
         }
     }
 
-    public DataObjectSerializerImplementation getSerializer(final Class<?> type) {
-        try {
-            return implementations.getUnchecked(type).newInstance();
-        } catch (InstantiationException | IllegalAccessException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     /**
      * Generates serializer source code for supplied container node,
      * which will read supplied binding type and invoke proper methods
@@ -309,7 +283,7 @@ public abstract class AbstractStreamWriterGenerator {
          *
          * @return Valid javassist code describing static serialization body.
          */
-        protected abstract CharSequence getStaticSerializeBody();
+        protected abstract CharSequence getSerializerBody();
 
         protected final CharSequence leafNode(final String localName, final CharSequence value) {
             return invoke(STREAM, "leafNode", escape(localName), value);
@@ -368,7 +342,7 @@ public abstract class AbstractStreamWriterGenerator {
 
 
         protected final CharSequence anyxmlNode(final String name, final String value) throws IllegalArgumentException {
-            return invoke(STREAM,"anyxmlNode",escape(name),name);
+            return invoke(STREAM, "anyxmlNode", escape(name),name);
         }
 
         protected final CharSequence endNode() {
@@ -384,19 +358,16 @@ public abstract class AbstractStreamWriterGenerator {
         }
 
         protected final CharSequence staticInvokeEmitter(final Type childType, final String name) {
-            Class<?> cls;
+            final Class<?> cls;
             try {
-                cls = strategy.loadClass(childType);
-                String className = implementations.getUnchecked(cls).getName();
-                return invoke(className, STATIC_SERIALIZE_METHOD_NAME,REGISTRY, name,STREAM);
+                cls = STRATEGY.loadClass(childType);
             } catch (ClassNotFoundException e) {
-                throw new IllegalStateException(e);
+                throw new IllegalStateException("Failed to invoke emitter", e);
             }
-        }
-    }
 
-    public void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
-        this.context = runtime;
+            String className = implementations.getUnchecked(cls).getName() + ".getInstance()";
+            return invoke(className, SERIALIZE_METHOD_NAME, REGISTRY, name, STREAM);
+        }
     }
 
 }
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerGenerator.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerGenerator.java
new file mode 100644 (file)
index 0000000..7ec766c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.binding.data.codec.gen.impl;
+
+import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
+import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
+
+/**
+ * Public interface exposed from generator implementation.
+ */
+public interface DataObjectSerializerGenerator {
+    /**
+     * Get a serializer for a particular type.
+     *
+     * @param type Type class
+     * @return Serializer instance.
+     */
+    DataObjectSerializerImplementation getSerializer(Class<?> type);
+
+    /**
+     * Notify the generator that the runtime context has been updated.
+     * @param runtime New runtime context
+     */
+    void onBindingRuntimeContextUpdated(BindingRuntimeContext runtime);
+}
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerPrototype.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerPrototype.java
new file mode 100644 (file)
index 0000000..2c7a908
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.binding.data.codec.gen.impl;
+
+import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
+import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
+
+/**
+ * Prototype of a DataObjectSerializerImplementation. This is a template class, which the
+ * {@link AbstractStreamWriterGenerator} uses to instantiate {@link DataObjectSerializerImplementation}
+ * on a per-type basis. During that time, the {@link #serialize(DataObjectSerializerRegistry, DataObject, BindingStreamEventWriter)}
+ * method will be replaced by the real implementation.
+ */
+public final class DataObjectSerializerPrototype implements DataObjectSerializerImplementation {
+    private static final DataObjectSerializerPrototype INSTANCE = new DataObjectSerializerPrototype();
+
+    public static DataObjectSerializerPrototype getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public void serialize(final DataObjectSerializerRegistry reg, final DataObject obj, final BindingStreamEventWriter stream) {
+        throw new UnsupportedOperationException("Prototype body, this code should never be invoked.");
+    }
+}
index f3fddd4b551e86e3b6861675aaa4769791ff2d10..bde9e63f5113eea3608dfca617c1bd040a92d37d 100644 (file)
@@ -76,7 +76,7 @@ public class StreamWriterGenerator extends AbstractStreamWriterGenerator {
         public abstract CharSequence emitStartEvent();
 
         @Override
-        protected CharSequence getStaticSerializeBody() {
+        protected CharSequence getSerializerBody() {
             StringBuilder b = new StringBuilder();
             b.append("{\n");
             b.append(statement(assign(DataObjectSerializerRegistry.class.getName(), REGISTRY, "$1")));
index 5f792d194c3bdc5d005d2a6c40a06575b66d9efa..4d19b4ea704da397a12b25f8ae387dc292722b86 100644 (file)
@@ -9,9 +9,11 @@ package org.opendaylight.yangtools.binding.data.codec.gen.spi;
 
 import com.google.common.collect.Iterators;
 import com.google.common.collect.UnmodifiableIterator;
+
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+
 import org.opendaylight.yangtools.sal.binding.model.api.Type;
 
 public abstract class AbstractSource {
@@ -30,7 +32,7 @@ public abstract class AbstractSource {
         StringBuilder builder = new StringBuilder();
         if (object != null) {
             builder.append(object);
-            builder.append(".");
+            builder.append('.');
         }
         builder.append(methodName);
         builder.append('(');
@@ -88,8 +90,7 @@ public abstract class AbstractSource {
         StringBuilder builder = new StringBuilder();
         builder.append("((");
         builder.append(type);
-        builder.append(')');
-        builder.append(' ');
+        builder.append(") ");
         builder.append(value);
         builder.append(')');
         return builder;
index 780e148d1b3e321da912f20db528096d3cc1e538..ffac120d8ee9dd182a2e26c76e2267b4da06e430 100644 (file)
@@ -11,14 +11,16 @@ import com.google.common.base.Preconditions;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+
 import java.util.AbstractMap.SimpleEntry;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeWriterFactory;
-import org.opendaylight.yangtools.binding.data.codec.gen.impl.AbstractStreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.DataObjectSerializerGenerator;
 import org.opendaylight.yangtools.concepts.Delegator;
 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
@@ -35,11 +37,11 @@ import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
 
 public class BindingNormalizedNodeCodecRegistry implements DataObjectSerializerRegistry, BindingNormalizedNodeWriterFactory, BindingNormalizedNodeSerializer {
 
-    private final AbstractStreamWriterGenerator generator;
+    private final DataObjectSerializerGenerator generator;
     private final LoadingCache<Class<? extends DataObject>, DataObjectSerializer> serializers;
     private BindingCodecContext codecContext;
 
-    public BindingNormalizedNodeCodecRegistry(final AbstractStreamWriterGenerator generator) {
+    public BindingNormalizedNodeCodecRegistry(final DataObjectSerializerGenerator generator) {
         this.generator = Preconditions.checkNotNull(generator);
         this.serializers = CacheBuilder.newBuilder().weakKeys().build(new GeneratorLoader());
     }
diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassCustomizer.java b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassCustomizer.java
new file mode 100644 (file)
index 0000000..22d9e38
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.sal.binding.generator.util;
+
+import com.google.common.annotations.Beta;
+
+import javassist.CtClass;
+
+/**
+ * Interface allowing customization of classes after loading.
+ */
+@Beta
+public interface ClassCustomizer {
+    /**
+     * Customize a class.
+     *
+     * @param cls Class to be customized
+     * @throws Exception when a problem ensues.
+     */
+    void customizeClass(CtClass cls) throws Exception;
+}
index 3bb61900df69b6dfd128d36edf6563fe074994d9..e6dc7623a89a917d3c6a68ff1f8c64b15350d773 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.yangtools.sal.binding.generator.util;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 
 import java.util.Collection;
@@ -116,6 +117,31 @@ public final class JavassistUtils {
         return target;
     }
 
+    /**
+     * Instantiate a new class based on a prototype. The class is set to automatically
+     * prune.
+     *
+     * @param prototype Prototype class fully qualified name
+     * @param fqn Target class fully qualified name
+     * @param customizer Customization callback to be invoked on the new class
+     * @return An instance of the new class
+     * @throws NotFoundException when the prototype class is not found
+     */
+    @Beta
+    public synchronized CtClass instantiatePrototype(final String prototype, final String fqn, final ClassCustomizer customizer) throws NotFoundException {
+        final CtClass result = classPool.getAndRename(prototype, fqn);
+        try {
+            customizer.customizeClass(result);
+        } catch (Exception e) {
+            LOG.warn("Failed to customize {} from prototype {}", fqn, prototype, e);
+            result.detach();
+            throw new IllegalStateException(String.format("Failed to instantiate prototype %s as %s", prototype, fqn), e);
+        }
+
+        result.stopPruning(false);
+        return result;
+    }
+
     public void implementsType(final CtClass it, final CtClass supertype) {
         Preconditions.checkArgument(supertype.isInterface(), "Supertype must be interface");
         it.addInterface(supertype);