Bug 1260: Implemented Binding Caching Codec
authorTony Tkacik <ttkacik@cisco.com>
Wed, 8 Apr 2015 17:41:05 +0000 (19:41 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Mon, 13 Apr 2015 08:10:25 +0000 (10:10 +0200)
Implemented Bidning Caching Codec which maintains
API-user configured cache of serialized data
from Bidning to NormalizedNode.

These cached data are used in subsequent serializations
in order to not allocate new NormalizedNodes for
logically equivalent data.

Change-Id: Iafaed957453569c3e8ecfbb2aed333f3a1fd418f
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
13 files changed:
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataNodeContainerSerializerSource.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/gen/impl/DataObjectSerializerSource.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/AbstractBindingNormalizedNodeCacheHolder.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCache.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCodecRegistry.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingToNormalizedStreamWriter.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/CachingNormalizedNodeCodec.java [moved from code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/CachingNormalizedNodeCodecImpl.java with 62% similarity]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/CachingNormalizedNodeSerializer.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/DataContainerCodecContext.java
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ForwardingBindingStreamEventWriter.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/NonCachingCodec.java [new file with mode: 0644]
code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/NormalizedNodeWriterWithAddChild.java [new file with mode: 0644]
code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/CachingCodecTest.java [new file with mode: 0644]

index 76d1cd01c6c79188eba4f470b992d453ec04cac0..4d52dab58deefab413fd78cd50a82ff7f8c2cc53 100644 (file)
@@ -15,6 +15,7 @@ import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature;
 import org.opendaylight.yangtools.sal.binding.model.api.ParameterizedType;
 import org.opendaylight.yangtools.sal.binding.model.api.Type;
 import org.opendaylight.yangtools.yang.binding.BindingMapping;
+import org.opendaylight.yangtools.yang.binding.BindingSerializer;
 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
@@ -54,12 +55,20 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
 
     @Override
     protected CharSequence getSerializerBody() {
-        StringBuilder b = new StringBuilder();
+        final StringBuilder b = new StringBuilder();
         b.append("{\n");
         b.append(statement(assign(DataObjectSerializerRegistry.class.getName(), REGISTRY, "$1")));
         b.append(statement(assign(dtoType.getFullyQualifiedName(), INPUT,
                 cast(dtoType.getFullyQualifiedName(), "$2"))));
         b.append(statement(assign(BindingStreamEventWriter.class.getName(), STREAM, cast(BindingStreamEventWriter.class.getName(), "$3"))));
+        b.append(statement(assign(BindingSerializer.class.getName(), SERIALIZER, null)));
+        b.append("if (");
+        b.append(STREAM);
+        b.append(" instanceof ");
+        b.append(BindingSerializer.class.getName());
+        b.append(") {");
+        b.append(statement(assign(SERIALIZER, cast(BindingSerializer.class.getName(), STREAM))));
+        b.append('}');
         b.append(statement(emitStartEvent()));
 
         emitBody(b);
@@ -79,10 +88,10 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
     }
 
     private static Map<String, Type> collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
-        for (MethodSignature definition : type.getMethodDefinitions()) {
+        for (final MethodSignature definition : type.getMethodDefinitions()) {
             hashMap.put(definition.getName(), definition.getReturnType());
         }
-        for (Type parent : type.getImplements()) {
+        for (final Type parent : type.getImplements()) {
             if (parent instanceof GeneratedType) {
                 collectAllProperties((GeneratedType) parent, hashMap);
             }
@@ -114,11 +123,11 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
     }
 
     private void emitBody(final StringBuilder b) {
-        Map<String, Type> getterToType = collectAllProperties(dtoType, new HashMap<String, Type>());
-        for (DataSchemaNode schemaChild : schemaNode.getChildNodes()) {
+        final Map<String, Type> getterToType = collectAllProperties(dtoType, new HashMap<String, Type>());
+        for (final DataSchemaNode schemaChild : schemaNode.getChildNodes()) {
             if (!schemaChild.isAugmenting()) {
-                String getter = getGetterName(schemaChild);
-                Type childType = getterToType.get(getter);
+                final String getter = getGetterName(schemaChild);
+                final Type childType = getterToType.get(getter);
                 emitChild(b, getter, childType, schemaChild);
             }
         }
@@ -141,22 +150,34 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
             b.append(statement(anyxmlNode(child.getQName().getLocalName(), getterName)));
         } else if (child instanceof LeafListSchemaNode) {
             b.append(statement(startLeafSet(child.getQName().getLocalName(),invoke(getterName, "size"))));
-            Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
+            final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
             b.append(forEach(getterName, valueType, statement(leafSetEntryNode(CURRENT))));
             b.append(statement(endNode()));
         } else if (child instanceof ListSchemaNode) {
-            Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
-            ListSchemaNode casted = (ListSchemaNode) child;
+            final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
+            final ListSchemaNode casted = (ListSchemaNode) child;
             emitList(b, getterName, valueType, casted);
         } else if (child instanceof ContainerSchemaNode) {
-            b.append(statement(staticInvokeEmitter(childType, getterName)));
+            b.append(tryToUseCacheElse(getterName,statement(staticInvokeEmitter(childType, getterName))));
         } else if (child instanceof ChoiceSchemaNode) {
-            String propertyName = CHOICE_PREFIX + childType.getName();
+            final String propertyName = CHOICE_PREFIX + childType.getName();
             staticConstant(propertyName, DataObjectSerializerImplementation.class, ChoiceDispatchSerializer.from(loadClass(childType)));
-            b.append(statement(invoke(propertyName, StreamWriterGenerator.SERIALIZE_METHOD_NAME, REGISTRY, cast(DataObject.class.getName(),getterName), STREAM)));
+            b.append(tryToUseCacheElse(getterName,statement(invoke(propertyName, StreamWriterGenerator.SERIALIZE_METHOD_NAME, REGISTRY, cast(DataObject.class.getName(),getterName), STREAM))));
         }
     }
 
+    private StringBuilder tryToUseCacheElse(final String getterName, final CharSequence statement) {
+        final StringBuilder b = new StringBuilder();
+
+        b.append("if ( ");
+        b.append(SERIALIZER).append("== null || ");
+        b.append(invoke(SERIALIZER, "serialize", getterName)).append("== null");
+        b.append(") {");
+        b.append(statement);
+        b.append("}");
+        return b;
+    }
+
     private void emitList(final StringBuilder b, final String getterName, final Type valueType,
             final ListSchemaNode child) {
         final CharSequence startEvent;
@@ -170,7 +191,7 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou
             startEvent = startMapNode(classReference(valueType), "_count");
         }
         b.append(statement(startEvent));
-        b.append(forEach(getterName, valueType, statement(staticInvokeEmitter(valueType, CURRENT))));
+        b.append(forEach(getterName, valueType, tryToUseCacheElse(CURRENT,statement(staticInvokeEmitter(valueType, CURRENT)))));
         b.append(statement(endNode()));
     }
 }
\ No newline at end of file
index ec276e79daa9fa03afd7dd64fc55e797bc59932b..b77fa9ac5eb7c9aadef97750b975135dfe304afa 100644 (file)
@@ -21,6 +21,7 @@ abstract class DataObjectSerializerSource extends AbstractSource {
 
     private static final ClassLoadingStrategy STRATEGY = GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy();
 
+    protected static final String SERIALIZER = "_serializer";
     protected static final String STREAM = "_stream";
     protected static final String ITERATOR = "_iterator";
     protected static final String CURRENT = "_current";
@@ -39,7 +40,7 @@ abstract class DataObjectSerializerSource extends AbstractSource {
     protected Class<? extends DataContainer> loadClass(final Type childType) {
         try {
             return (Class<? extends DataContainer>) STRATEGY.loadClass(childType);
-        } catch (ClassNotFoundException e) {
+        } catch (final ClassNotFoundException e) {
             throw new IllegalStateException("Could not load referenced class ", e);
         }
     }
@@ -131,11 +132,11 @@ abstract class DataObjectSerializerSource extends AbstractSource {
         final Class<?> cls;
         try {
             cls = STRATEGY.loadClass(childType);
-        } catch (ClassNotFoundException e) {
+        } catch (final ClassNotFoundException e) {
             throw new IllegalStateException("Failed to invoke emitter", e);
         }
 
-        String className = this.generator.loadSerializerFor(cls) + ".getInstance()";
+        final String className = this.generator.loadSerializerFor(cls) + ".getInstance()";
         return invoke(className, AbstractStreamWriterGenerator.SERIALIZE_METHOD_NAME, REGISTRY, name, STREAM);
     }
 }
\ No newline at end of file
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/AbstractBindingNormalizedNodeCacheHolder.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/AbstractBindingNormalizedNodeCacheHolder.java
new file mode 100644 (file)
index 0000000..07d7464
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+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.Set;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+/**
+ *
+ * Abstract Holder of Binding to Normalized Node caches indexed by {@link DataContainerCodecContext}
+ * to which cache is associated.
+ *
+ */
+abstract class AbstractBindingNormalizedNodeCacheHolder {
+
+    private final Set<Class<? extends DataObject>> cachedValues;
+    private final LoadingCache<DataContainerCodecContext<?, ?>, BindingNormalizedNodeCache> caches = CacheBuilder
+            .newBuilder().build(new CacheLoader<DataContainerCodecContext<?, ?>, BindingNormalizedNodeCache>() {
+
+                @Override
+                public BindingNormalizedNodeCache load(final DataContainerCodecContext<?, ?> key) throws Exception {
+                    return new BindingNormalizedNodeCache(AbstractBindingNormalizedNodeCacheHolder.this, key);
+                }
+
+            });
+
+    protected AbstractBindingNormalizedNodeCacheHolder(final Set<Class<? extends DataObject>> cacheSpec) {
+        cachedValues = Preconditions.checkNotNull(cacheSpec);
+    }
+
+    BindingNormalizedNodeCache getCachingSerializer(final DataContainerCodecContext<?, ?> childCtx) {
+        if (isCached(childCtx.getBindingClass())) {
+            return caches.getUnchecked(childCtx);
+        }
+        return null;
+    }
+
+    boolean isCached(final Class<?> type) {
+        return cachedValues.contains(type);
+    }
+}
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCache.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingNormalizedNodeCache.java
new file mode 100644 (file)
index 0000000..141353d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+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 org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+final class BindingNormalizedNodeCache extends CacheLoader<DataObject, NormalizedNode<?, ?>> {
+
+    private final LoadingCache<DataObject, NormalizedNode<?, ?>> cache = CacheBuilder.newBuilder().weakValues()
+            .build(this);
+    final DataContainerCodecContext<?, ?> subtreeRoot;
+    final AbstractBindingNormalizedNodeCacheHolder cacheHolder;
+
+    public BindingNormalizedNodeCache(final AbstractBindingNormalizedNodeCacheHolder cacheHolder,
+            final DataContainerCodecContext<?, ?> subtreeRoot) {
+        this.cacheHolder = Preconditions.checkNotNull(cacheHolder, "cacheHolder");
+        this.subtreeRoot = Preconditions.checkNotNull(subtreeRoot, "subtreeRoot");
+    }
+
+    @Override
+    public NormalizedNode<?, ?> load(final DataObject key) throws Exception {
+        return CachingNormalizedNodeSerializer.serializeUsingStreamWriter(cacheHolder, subtreeRoot, key);
+    }
+
+    /**
+     * Returns cached NormalizedNode representation of DataObject.
+     *
+     * If representation is not cached, serializes DataObject and updates cache with representation.
+     *
+     * @param obj Binding object to be deserialized
+     * @return NormalizedNode representation of binding object.
+     */
+    NormalizedNode<?, ?> get(final DataObject obj) {
+        return cache.getUnchecked(obj);
+    }
+}
index 0c59f342bc74e10371494e315432964da30740a3..50af39cfbf886c2241cd1cfcdd67343f388295ef 100644 (file)
@@ -18,6 +18,7 @@ import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map.Entry;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTree;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTreeFactory;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeWriterFactory;
@@ -71,7 +72,7 @@ public class BindingNormalizedNodeCodecRegistry implements DataObjectSerializerR
         return serializers.getUnchecked(type);
     }
 
-    public BindingCodecContext getCodecContext() {
+    public BindingCodecTree getCodecContext() {
         return codecContext;
     }
 
@@ -231,23 +232,23 @@ public class BindingNormalizedNodeCodecRegistry implements DataObjectSerializerR
     }
 
     @Override
-    public BindingCodecContext create(BindingRuntimeContext context) {
+    public BindingCodecContext create(final BindingRuntimeContext context) {
         return new BindingCodecContext(context, this);
     }
 
     @Override
-    public BindingCodecContext create(SchemaContext context, Class<?>... bindingClasses) {
-        ModuleInfoBackedContext strategy = ModuleInfoBackedContext.create();
-        for (Class<?> bindingCls : bindingClasses) {
+    public BindingCodecContext create(final SchemaContext context, final Class<?>... bindingClasses) {
+        final ModuleInfoBackedContext strategy = ModuleInfoBackedContext.create();
+        for (final Class<?> bindingCls : bindingClasses) {
             try {
                 strategy.registerModuleInfo(BindingReflections.getModuleInfo(bindingCls));
-            } catch (Exception e) {
+            } catch (final Exception e) {
                 throw new IllegalStateException(
                         "Could not create BindingRuntimeContext from class " + bindingCls.getName(),
                         e);
             }
         }
-        BindingRuntimeContext runtimeCtx = BindingRuntimeContext.create(strategy, context);
+        final BindingRuntimeContext runtimeCtx = BindingRuntimeContext.create(strategy, context);
         return create(runtimeCtx);
     }
 
index 617b514ccd4c2f1c13bfabd8319a61bc628255f8..ed4bb470024eebbc1344f2d0c10c4eb6d5cdff46 100644 (file)
@@ -39,7 +39,7 @@ class BindingToNormalizedStreamWriter implements BindingStreamEventWriter, Deleg
 
     }
 
-    private NodeCodecContext<?> current() {
+    protected final NodeCodecContext<?> current() {
         return schema.peek();
     }
 
@@ -13,25 +13,26 @@ import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeCa
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
-class CachingNormalizedNodeCodecImpl<D extends DataObject> implements BindingNormalizedNodeCachingCodec<D>{
+class CachingNormalizedNodeCodec<D extends DataObject> extends AbstractBindingNormalizedNodeCacheHolder implements
+        BindingNormalizedNodeCachingCodec<D> {
 
-    private final Set<Class<? extends DataObject>> cachedValues;
     private final DataContainerCodecContext<D, ?> context;
 
-    CachingNormalizedNodeCodecImpl(DataContainerCodecContext<D, ?> subtreeRoot, Set<Class<? extends DataObject>> cacheSpec) {
+
+    CachingNormalizedNodeCodec(final DataContainerCodecContext<D, ?> subtreeRoot,
+            final Set<Class<? extends DataObject>> cacheSpec) {
+        super(cacheSpec);
         this.context = Preconditions.checkNotNull(subtreeRoot);
-        this.cachedValues = Preconditions.checkNotNull(cacheSpec);
     }
 
     @Override
-    public D deserialize(NormalizedNode<?, ?> data) {
+    public D deserialize(final NormalizedNode<?, ?> data) {
         return context.deserialize(data);
     }
 
     @Override
-    public NormalizedNode<?, ?> serialize(D data) {
-        // FIXME: Add real-class based serialization.
-        return context.serialize(data);
+    public NormalizedNode<?, ?> serialize(final D data) {
+        return CachingNormalizedNodeSerializer.serialize(this, context, data);
     }
 
     @Override
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/CachingNormalizedNodeSerializer.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/CachingNormalizedNodeSerializer.java
new file mode 100644 (file)
index 0000000..87c8c44
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.binding.BindingSerializer;
+import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+
+/**
+ * Serializer of Binding objects to Normalized Node which uses {@link BindingNormalizedNodeCache} to
+ * cache already serialized values.
+ *
+ * This serializer implements {@link BindingStreamEventWriter} along with {@link BindingSerializer}.
+ *
+ * {@link BindingSerializer} interface is used by generated implementations of
+ * {@link org.opendaylight.yangtools.yang.binding.DataObjectSerializer} to provide Binding object
+ * for inspection and to prevent streaming of already serialized object.
+ *
+ */
+final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEventWriter implements
+        BindingSerializer<Object, DataObject> {
+
+    private final NormalizedNodeResult domResult;
+    private final NormalizedNodeWriterWithAddChild domWriter;
+    private final BindingToNormalizedStreamWriter delegate;
+    private final AbstractBindingNormalizedNodeCacheHolder cacheHolder;
+
+    CachingNormalizedNodeSerializer(final AbstractBindingNormalizedNodeCacheHolder cacheHolder,
+            final DataContainerCodecContext<?, ?> subtreeRoot) {
+        this.cacheHolder = cacheHolder;
+        this.domResult = new NormalizedNodeResult();
+        this.domWriter = new NormalizedNodeWriterWithAddChild(domResult);
+        this.delegate = new BindingToNormalizedStreamWriter(subtreeRoot, domWriter);
+    }
+
+    @Override
+    protected BindingStreamEventWriter delegate() {
+        return delegate;
+    }
+
+    NormalizedNode<?, ?> build() {
+        return domResult.getResult();
+    }
+
+    /**
+     * Serializes input if it is cached, returns null otherwise.
+     *
+     * If input is cached it uses {@link NormalizedNodeWriterWithAddChild#addChild(NormalizedNode)}
+     * to provide already serialized value to underlying NormalizedNodeWriter in order to reuse
+     * value instead of creating new one using Normalized Node stream APIs.
+     *
+     * Note that this optional is serialization of child node invoked from
+     * {@link org.opendaylight.yangtools.yang.binding.DataObjectSerializer}, which may opt-out from
+     * streaming of data when non-null result is returned.
+     */
+    @Override
+    public NormalizedNode<?, ?> serialize(final DataObject input) {
+        final BindingNormalizedNodeCache cachingSerializer = getCacheSerializer(input.getImplementedInterface());
+        if (cachingSerializer != null) {
+            final NormalizedNode<?, ?> domData = cachingSerializer.get(input);
+            domWriter.addChild(domData);
+            return domData;
+        }
+        return null;
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    private BindingNormalizedNodeCache getCacheSerializer(final Class type) {
+        if (cacheHolder.isCached(type)) {
+            final DataContainerCodecContext<?, ?> currentCtx = (DataContainerCodecContext<?, ?>) delegate.current();
+            if (type.equals(currentCtx.getBindingClass())) {
+                return cacheHolder.getCachingSerializer(currentCtx);
+            }
+            final DataContainerCodecContext<?, ?> childCtx =
+                    (DataContainerCodecContext<?, ?>) currentCtx.streamChild(type);
+            return cacheHolder.getCachingSerializer(childCtx);
+        }
+        return null;
+    }
+
+    /**
+     * Serializes supplied data using stream writer with child cache enabled or using cache directly
+     * if cache is avalaible also for supplied Codec node.
+     *
+     * @param cacheHolder Binding to Normalized Node Cache holder
+     * @param subtreeRoot Codec Node for provided data object
+     * @param data Data to be serialized
+     * @return Normalized Node representation of data.
+     */
+    static NormalizedNode<?, ?> serialize(final AbstractBindingNormalizedNodeCacheHolder cacheHolder,
+            final DataContainerCodecContext<?, ?> subtreeRoot, final DataObject data) {
+        final BindingNormalizedNodeCache cache = cacheHolder.getCachingSerializer(subtreeRoot);
+        if (cache != null) {
+            return cache.get(data);
+        }
+        return serializeUsingStreamWriter(cacheHolder, subtreeRoot, data);
+    }
+
+    /**
+     * Serializes supplied data using stream writer with child cache enabled.
+     *
+     * @param cacheHolder Binding to Normalized Node Cache holder
+     * @param subtreeRoot Codec Node for provided data object
+     * @param data Data to be serialized
+     * @return Normalized Node representation of data.
+     */
+    static NormalizedNode<?, ?> serializeUsingStreamWriter(final AbstractBindingNormalizedNodeCacheHolder cacheHolder,
+            final DataContainerCodecContext<?, ?> subtreeRoot, final DataObject data) {
+        final CachingNormalizedNodeSerializer writer = new CachingNormalizedNodeSerializer(cacheHolder, subtreeRoot);
+        try {
+            subtreeRoot.eventStreamSerializer().serialize(data, writer);
+            return writer.build();
+        } catch (final IOException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+}
\ No newline at end of file
index 455cf674bbf7494ab335bc1123ad2d99bafddb31..371a181ce4111bc96f195d32fceb0f3a092b9a76 100644 (file)
@@ -135,11 +135,14 @@ abstract class DataContainerCodecContext<D extends DataObject,T> extends NodeCod
 
     @Override
     public BindingNormalizedNodeCachingCodec<D> createCachingCodec(
-            ImmutableCollection<Class<? extends DataObject>> cacheSpecifier) {
-        return new CachingNormalizedNodeCodecImpl<D>(this,ImmutableSet.copyOf(cacheSpecifier));
+            final ImmutableCollection<Class<? extends DataObject>> cacheSpecifier) {
+        if(cacheSpecifier.isEmpty()) {
+            return new NonCachingCodec<>(this);
+        }
+        return new CachingNormalizedNodeCodec<D>(this,ImmutableSet.copyOf(cacheSpecifier));
     }
 
-    BindingStreamEventWriter createWriter(NormalizedNodeStreamWriter domWriter) {
+    BindingStreamEventWriter createWriter(final NormalizedNodeStreamWriter domWriter) {
         return  new BindingToNormalizedStreamWriter(this, domWriter);
     }
 
@@ -151,7 +154,7 @@ abstract class DataContainerCodecContext<D extends DataObject,T> extends NodeCod
     }
 
     @Override
-    public NormalizedNode<?, ?> serialize(D data) {
+    public NormalizedNode<?, ?> serialize(final D data) {
         final NormalizedNodeResult result = new NormalizedNodeResult();
         // We create DOM stream writer which produces normalized nodes
         final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
@@ -160,10 +163,10 @@ abstract class DataContainerCodecContext<D extends DataObject,T> extends NodeCod
     }
 
     @Override
-    public void writeAsNormalizedNode(D data, NormalizedNodeStreamWriter writer) {
+    public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) {
         try {
             eventStreamSerializer().serialize(data, createWriter(writer));
-        } catch (IOException e) {
+        } catch (final IOException e) {
             throw new IllegalStateException("Failed to serialize Binding DTO",e);
         }
     }
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ForwardingBindingStreamEventWriter.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/ForwardingBindingStreamEventWriter.java
new file mode 100644 (file)
index 0000000..d6323df
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+
+//FIXME: Consider moving this to yang.binding.util.* in Be
+abstract class ForwardingBindingStreamEventWriter implements BindingStreamEventWriter {
+
+    protected abstract BindingStreamEventWriter delegate();
+
+    @Override
+    public void leafNode(final String localName, final Object value) throws IOException, IllegalArgumentException {
+        delegate().leafNode(localName, value);
+    }
+
+
+    @Override
+    public void startLeafSet(final String localName, final int childSizeHint) throws IOException, IllegalArgumentException {
+        delegate().startLeafSet(localName, childSizeHint);
+    }
+
+
+    @Override
+    public void leafSetEntryNode(final Object value) throws IOException, IllegalArgumentException {
+        delegate().leafSetEntryNode(value);
+    }
+
+
+    @Override
+    public void startContainerNode(final Class<? extends DataObject> container, final int childSizeHint) throws IOException,
+            IllegalArgumentException {
+        delegate().startContainerNode(container, childSizeHint);
+    }
+
+
+    @Override
+    public void startUnkeyedList(final Class<? extends DataObject> localName, final int childSizeHint) throws IOException,
+            IllegalArgumentException {
+        delegate().startUnkeyedList(localName, childSizeHint);
+    }
+
+
+    @Override
+    public void startUnkeyedListItem(final int childSizeHint) throws IOException, IllegalStateException {
+        delegate().startUnkeyedListItem(childSizeHint);
+    }
+
+
+    @Override
+    public <T extends DataObject & Identifiable<?>> void startMapNode(final Class<T> mapEntryType, final int childSizeHint)
+            throws IOException, IllegalArgumentException {
+        delegate().startMapNode(mapEntryType, childSizeHint);
+    }
+
+
+    @Override
+    public <T extends DataObject & Identifiable<?>> void startOrderedMapNode(final Class<T> mapEntryType, final int childSizeHint)
+            throws IOException, IllegalArgumentException {
+        delegate().startOrderedMapNode(mapEntryType, childSizeHint);
+    }
+
+
+    @Override
+    public void startMapEntryNode(final Identifier<?> keyValues, final int childSizeHint) throws IOException,
+            IllegalArgumentException {
+        delegate().startMapEntryNode(keyValues, childSizeHint);
+    }
+
+
+    @Override
+    public void startChoiceNode(final Class<? extends DataContainer> choice, final int childSizeHint) throws IOException,
+            IllegalArgumentException {
+        delegate().startChoiceNode(choice, childSizeHint);
+    }
+
+
+    @Override
+    public void startCase(final Class<? extends DataObject> caze, final int childSizeHint) throws IOException,
+            IllegalArgumentException {
+        delegate().startCase(caze, childSizeHint);
+    }
+
+
+    @Override
+    public void startAugmentationNode(final Class<? extends Augmentation<?>> augmentationType) throws IOException,
+            IllegalArgumentException {
+        delegate().startAugmentationNode(augmentationType);
+    }
+
+
+    @Override
+    public void anyxmlNode(final String name, final Object value) throws IOException, IllegalArgumentException {
+        delegate().anyxmlNode(name, value);
+    }
+
+
+    @Override
+    public void endNode() throws IOException, IllegalStateException {
+        delegate().endNode();
+    }
+
+
+    @Override
+    public void flush() throws IOException {
+        delegate().flush();
+    }
+
+
+    @Override
+    public void close() throws IOException {
+        delegate().close();
+    }
+}
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/NonCachingCodec.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/NonCachingCodec.java
new file mode 100644 (file)
index 0000000..4216f58
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeCachingCodec;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeCodec;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+class NonCachingCodec<D extends DataObject> implements BindingNormalizedNodeCachingCodec<D> {
+
+    private final BindingNormalizedNodeCodec<D> delegate;
+
+    protected NonCachingCodec(final BindingNormalizedNodeCodec<D> delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public D deserialize(final NormalizedNode<?, ?> data) {
+        return delegate.deserialize(data);
+    }
+
+    @Override
+    public NormalizedNode<?, ?> serialize(final D data) {
+        return delegate.serialize(data);
+    }
+
+    @Override
+    public void close() {
+        // NOOP
+    }
+
+}
diff --git a/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/NormalizedNodeWriterWithAddChild.java b/code-generator/binding-data-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/NormalizedNodeWriterWithAddChild.java
new file mode 100644 (file)
index 0000000..57f9bcf
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2015 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.impl;
+
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+
+final class NormalizedNodeWriterWithAddChild extends ImmutableNormalizedNodeStreamWriter {
+
+    NormalizedNodeWriterWithAddChild(final NormalizedNodeResult result) {
+        super(result);
+    }
+
+    void addChild(final NormalizedNode<?, ?> child) {
+        this.writeChild(child);
+    }
+}
diff --git a/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/CachingCodecTest.java b/code-generator/binding-data-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/CachingCodecTest.java
new file mode 100644 (file)
index 0000000..735679a
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2015 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.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.List;
+import javassist.ClassPool;
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.Top;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.TopBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelListBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.yangtools.test.binding.rev140701.two.level.list.TopLevelListKey;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTreeNode;
+import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeCachingCodec;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class CachingCodecTest extends AbstractBindingRuntimeTest {
+
+    private static final NodeIdentifier TOP_LEVEL_LIST_ARG = new NodeIdentifier(TopLevelList.QNAME);
+    private static final InstanceIdentifier<Top> TOP_PATH = InstanceIdentifier.create(Top.class);
+    private static final List<TopLevelList> TWO_LIST = createList(2);
+    private static final List<TopLevelList> THREE_LIST = createList(3);
+
+    private static final Top TOP_TWO_LIST_DATA = new TopBuilder().setTopLevelList(TWO_LIST).build();
+    private static final Top TOP_THREE_LIST_DATA = new TopBuilder().setTopLevelList(THREE_LIST).build();
+
+    private BindingNormalizedNodeCodecRegistry registry;
+    private BindingCodecTreeNode<Top> topNode;
+
+    @Override
+    public void setup() {
+        super.setup();
+        final JavassistUtils utils = JavassistUtils.forClassPool(ClassPool.getDefault());
+        registry = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(utils));
+        registry.onBindingRuntimeContextUpdated(getRuntimeContext());
+        topNode = registry.getCodecContext().getSubtreeCodec(TOP_PATH);
+
+    }
+
+
+    private static List<TopLevelList> createList(final int num) {
+
+        final ImmutableList.Builder<TopLevelList> builder = ImmutableList.builder();
+        for (int i = 0; i < num; i++) {
+            final TopLevelListKey key = new TopLevelListKey("test-" + i);
+            builder.add(new TopLevelListBuilder().setKey(key).build());
+        }
+        return builder.build();
+    }
+
+    @Test
+    public void testListCache() {
+        final BindingNormalizedNodeCachingCodec<Top> cachingCodec = createCachingCodec(TopLevelList.class);
+        final NormalizedNode<?, ?> first = cachingCodec.serialize(TOP_TWO_LIST_DATA);
+        final NormalizedNode<?, ?> second = cachingCodec.serialize(TOP_TWO_LIST_DATA);
+
+        assertNotSame(first, second);
+        assertEquals(first, second);
+        verifyListItemSame(first, second);
+
+        final NormalizedNode<?, ?> third = cachingCodec.serialize(TOP_THREE_LIST_DATA);
+        verifyListItemSame(first, third);
+        verifyListItemSame(second, third);
+    }
+
+
+    @Test
+    public void testTopAndListCache() {
+        final BindingNormalizedNodeCachingCodec<Top> cachingCodec = createCachingCodec(Top.class, TopLevelList.class);
+        final NormalizedNode<?, ?> first = cachingCodec.serialize(TOP_TWO_LIST_DATA);
+        final NormalizedNode<?, ?> second = cachingCodec.serialize(TOP_TWO_LIST_DATA);
+
+        assertEquals(first, second);
+        assertSame(first, second);
+
+        final NormalizedNode<?, ?> third = cachingCodec.serialize(TOP_THREE_LIST_DATA);
+        verifyListItemSame(first, third);
+    }
+
+    @SafeVarargs
+    private final BindingNormalizedNodeCachingCodec<Top> createCachingCodec(
+            final Class<? extends DataObject>... classes) {
+        return topNode.createCachingCodec(ImmutableSet.<Class<? extends DataObject>>copyOf(classes));
+    }
+
+    private static void verifyListItemSame(final NormalizedNode<?, ?> firstTop, final NormalizedNode<?, ?> secondTop) {
+        final Collection<MapEntryNode> initialNodes = getListItems(firstTop).getValue();
+        final MapNode secondMap = getListItems(secondTop);
+
+        for (final MapEntryNode initial : initialNodes) {
+            final MapEntryNode second = secondMap.getChild(initial.getIdentifier()).get();
+            assertEquals(initial, second);
+            assertSame(initial, second);
+        }
+    }
+
+
+    private static MapNode getListItems(final NormalizedNode<?, ?> top) {
+        return ((MapNode) ((DataContainerNode<?>) top).getChild(TOP_LEVEL_LIST_ARG).get());
+    }
+
+}