From 55444a2765f6fd10a9222e95ab9781eed8473161 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 5 Aug 2014 16:37:01 +0200 Subject: [PATCH] BUG-1487: use prototypes for instantiating codecs 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 --- .../impl/AbstractStreamWriterGenerator.java | 133 +++++++----------- .../impl/DataObjectSerializerGenerator.java | 30 ++++ .../impl/DataObjectSerializerPrototype.java | 32 +++++ .../codec/gen/impl/StreamWriterGenerator.java | 2 +- .../data/codec/gen/spi/AbstractSource.java | 7 +- .../BindingNormalizedNodeCodecRegistry.java | 8 +- .../generator/util/ClassCustomizer.java | 26 ++++ .../generator/util/JavassistUtils.java | 26 ++++ 8 files changed, 176 insertions(+), 88 deletions(-) create mode 100644 code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerGenerator.java create mode 100644 code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerPrototype.java create mode 100644 code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassCustomizer.java diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/AbstractStreamWriterGenerator.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/AbstractStreamWriterGenerator.java index be35585869..8fa66ce6b7 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/AbstractStreamWriterGenerator.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/AbstractStreamWriterGenerator.java @@ -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> 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 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 loadClass(final Type childType) { try { - return (Class) strategy.loadClass(childType); + return (Class) 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 index 0000000000..7ec766c916 --- /dev/null +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerGenerator.java @@ -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 index 0000000000..2c7a908552 --- /dev/null +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerPrototype.java @@ -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."); + } +} diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/StreamWriterGenerator.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/StreamWriterGenerator.java index f3fddd4b55..bde9e63f51 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/StreamWriterGenerator.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/StreamWriterGenerator.java @@ -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"))); diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/spi/AbstractSource.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/spi/AbstractSource.java index 5f792d194c..4d19b4ea70 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/spi/AbstractSource.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/spi/AbstractSource.java @@ -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; diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCodecRegistry.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCodecRegistry.java index 780e148d1b..ffac120d8e 100644 --- a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCodecRegistry.java +++ b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCodecRegistry.java @@ -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, 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 index 0000000000..22d9e387dd --- /dev/null +++ b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/ClassCustomizer.java @@ -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; +} diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/JavassistUtils.java b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/JavassistUtils.java index 3bb61900df..e6dc7623a8 100644 --- a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/JavassistUtils.java +++ b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/util/JavassistUtils.java @@ -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); -- 2.36.6