BUG-7996: Split JSONCodec into multiple implementations 73/53373/8
authorRobert Varga <rovarga@cisco.com>
Wed, 15 Mar 2017 18:10:18 +0000 (19:10 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 23 Mar 2017 09:26:37 +0000 (10:26 +0100)
Make JSONCodecFactory an abstract base class with four different
trade-offs in cpu/memory usage. The only implementation available
to clients is SharedJSONCodecFactory, which behaves exactly like
the previous implementation used to.

One user-visible change is that thread-safe implementations,
including SharedJSONCodedFactory, now keep a shared weak cache
for reuse when the same kind of factory is requested multiple times
for the same context.

Change-Id: I15342e6b4ed68ccbac9c099f63654d7456913934
Signed-off-by: Robert Varga <rovarga@cisco.com>
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/EagerJSONCodecFactory.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LazyJSONCodecFactory.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SharedJSONCodecFactory.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleJSONCodecFactory.java [new file with mode: 0644]

diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/EagerJSONCodecFactory.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/EagerJSONCodecFactory.java
new file mode 100644 (file)
index 0000000..2600521
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017 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.yang.data.codec.gson;
+
+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.Map;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
+
+/**
+ * Pre-computed JSONCodecFactory. All possible codecs are created upfront at instantiation time, after which they
+ * are available for the cost of a constant lookup.
+ *
+ * @author Robert Varga
+ */
+@ThreadSafe
+final class EagerJSONCodecFactory extends JSONCodecFactory {
+    // Weak keys to retire the entry when SchemaContext goes away
+    // Soft values to keep unreferenced factories around for a bit
+    private static final LoadingCache<SchemaContext, EagerJSONCodecFactory> CACHE = CacheBuilder.newBuilder()
+            .weakKeys().softValues().build(new CacheLoader<SchemaContext, EagerJSONCodecFactory>() {
+                @Override
+                public EagerJSONCodecFactory load(final SchemaContext key) {
+                    return new EagerJSONCodecFactory(key);
+                }
+            });
+
+    private final Map<TypedSchemaNode, JSONCodec<?>> codecs;
+
+    EagerJSONCodecFactory(final SchemaContext context) {
+        super(context);
+        this.codecs = constructCodecs(context);
+    }
+
+    static EagerJSONCodecFactory getIfPresent(final SchemaContext context) {
+        return CACHE.getIfPresent(context);
+    }
+
+    static EagerJSONCodecFactory get(final SchemaContext context) {
+        return CACHE.getUnchecked(context);
+    }
+
+    @Override
+    JSONCodec<?> codecFor(final TypedSchemaNode schema) {
+        final JSONCodec<?> ret = codecs.get(schema);
+        Preconditions.checkArgument(ret != null, "No codec available for schema %s", schema);
+        return ret;
+    }
+
+    private static Map<TypedSchemaNode, JSONCodec<?>> constructCodecs(final SchemaContext context) {
+        final LazyJSONCodecFactory lazy = new LazyJSONCodecFactory(context);
+        requestCodecsForChildren(lazy, context);
+        return lazy.getCodecs();
+    }
+
+    private static void requestCodecsForChildren(final LazyJSONCodecFactory factory, final DataNodeContainer parent) {
+        for (DataSchemaNode child : parent.getChildNodes()) {
+            if (child instanceof TypedSchemaNode) {
+                factory.codecFor(child);
+            } else if (child instanceof DataNodeContainer) {
+                requestCodecsForChildren(factory, (DataNodeContainer)child);
+            }
+        }
+    }
+}
index 1962556feac5ba6a75ba7fe1a1713cf8cd09603a..fd1bf502d90c70471a1d09683464570d209fa865 100644 (file)
@@ -10,13 +10,8 @@ package org.opendaylight.yangtools.yang.data.codec.gson;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
 import com.google.gson.stream.JsonWriter;
 import java.io.IOException;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -36,7 +31,7 @@ import org.slf4j.LoggerFactory;
  * a particular {@link SchemaContext}, but can be reused by multiple {@link JSONNormalizedNodeStreamWriter}s.
  */
 @Beta
-public final class JSONCodecFactory {
+public abstract class JSONCodecFactory {
     private static final Logger LOG = LoggerFactory.getLogger(JSONCodecFactory.class);
     private static final JSONCodec<Object> NULL_CODEC = new JSONCodec<Object>() {
         @Override
@@ -56,19 +51,10 @@ public final class JSONCodecFactory {
         }
     };
 
-    private final LoadingCache<TypedSchemaNode, JSONCodec<?>> codecs = CacheBuilder.newBuilder().softValues()
-            .build(new CacheLoader<TypedSchemaNode, JSONCodec<?>>() {
-        @Override
-        public JSONCodec<?> load(@Nonnull final TypedSchemaNode key) throws Exception {
-            final TypeDefinition<?> type = key.getType();
-            return createCodec(key, type);
-        }
-    });
-
     private final SchemaContext schemaContext;
     private final JSONCodec<?> iidCodec;
 
-    private JSONCodecFactory(final SchemaContext context) {
+    JSONCodecFactory(final SchemaContext context) {
         this.schemaContext = Preconditions.checkNotNull(context);
         iidCodec = new JSONStringInstanceIdentifierCodec(context, this);
     }
@@ -80,67 +66,55 @@ public final class JSONCodecFactory {
      * @return A codec factory instance.
      */
     public static JSONCodecFactory create(final SchemaContext context) {
-        return new JSONCodecFactory(context);
+        return SharedJSONCodecFactory.get(context);
     }
 
-    private JSONCodec<?> createCodec(final DataSchemaNode key, final TypeDefinition<?> type) {
+    final JSONCodec<?> createCodec(final DataSchemaNode key, final TypeDefinition<?> type) {
         if (type instanceof LeafrefTypeDefinition) {
             return createReferencedTypeCodec(key, (LeafrefTypeDefinition) type);
         } else if (type instanceof IdentityrefTypeDefinition) {
-            return createIdentityrefTypeCodec(key);
+            return new JSONStringIdentityrefCodec(schemaContext, key.getQName().getModule());
         } else if (type instanceof UnionTypeDefinition) {
             return createUnionTypeCodec(key, (UnionTypeDefinition) type);
-        }
-        return createFromSimpleType(key, type);
-    }
-
-    private JSONCodec<?> createReferencedTypeCodec(final DataSchemaNode schema,
-            final LeafrefTypeDefinition type) {
-        // FIXME: Verify if this does indeed support leafref of leafref
-        final TypeDefinition<?> referencedType =
-                SchemaContextUtil.getBaseTypeForLeafRef(type, getSchemaContext(), schema);
-        Verify.verifyNotNull(referencedType, "Unable to find base type for leafref node '%s'.", schema.getPath());
-        return createCodec(schema, referencedType);
-    }
-
-    private JSONCodec<QName> createIdentityrefTypeCodec(final DataSchemaNode schema) {
-        final JSONCodec<QName> jsonStringIdentityrefCodec =
-                new JSONStringIdentityrefCodec(schemaContext, schema.getQName().getModule());
-        return jsonStringIdentityrefCodec;
-    }
-
-    private JSONCodec<Object> createUnionTypeCodec(final DataSchemaNode schema, final UnionTypeDefinition type) {
-        final JSONCodec<Object> jsonStringUnionCodec = new JSONStringUnionCodec(schema, type, this);
-        return jsonStringUnionCodec;
-    }
-
-    private JSONCodec<?> createFromSimpleType(final DataSchemaNode schema, final TypeDefinition<?> type) {
-        if (type instanceof InstanceIdentifierTypeDefinition) {
+        } else if (type instanceof InstanceIdentifierTypeDefinition) {
             return iidCodec;
-        }
-        if (type instanceof EmptyTypeDefinition) {
+        } else if (type instanceof EmptyTypeDefinition) {
             return JSONEmptyCodec.INSTANCE;
         }
 
         final TypeDefinitionAwareCodec<Object, ?> codec = TypeDefinitionAwareCodec.from(type);
         if (codec == null) {
-            LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName()
-                    .getLocalName());
+            // catches anyxml
+            LOG.debug("Codec for {} is not implemented yet", type);
             return NULL_CODEC;
         }
         return AbstractJSONCodec.create(codec);
     }
 
-    SchemaContext getSchemaContext() {
+    final SchemaContext getSchemaContext() {
         return schemaContext;
     }
 
     JSONCodec<?> codecFor(final DataSchemaNode schema) {
         Preconditions.checkArgument(schema instanceof TypedSchemaNode, "Unsupported node type %s", schema.getClass());
-        return codecs.getUnchecked((TypedSchemaNode) schema);
+        return codecFor((TypedSchemaNode) schema);
     }
 
-    JSONCodec<?> codecFor(final DataSchemaNode schema, final TypeDefinition<?> unionSubType) {
+    abstract JSONCodec<?> codecFor(final TypedSchemaNode schema);
+
+    final JSONCodec<?> codecFor(final DataSchemaNode schema, final TypeDefinition<?> unionSubType) {
         return createCodec(schema, unionSubType);
     }
+
+    private JSONCodec<?> createReferencedTypeCodec(final DataSchemaNode schema, final LeafrefTypeDefinition type) {
+        // FIXME: Verify if this does indeed support leafref of leafref
+        final TypeDefinition<?> referencedType = SchemaContextUtil.getBaseTypeForLeafRef(type, schemaContext, schema);
+        Verify.verifyNotNull(referencedType, "Unable to find base type for leafref node '%s'.", schema.getPath());
+        return createCodec(schema, referencedType);
+    }
+
+    private JSONCodec<Object> createUnionTypeCodec(final DataSchemaNode schema, final UnionTypeDefinition type) {
+        final JSONCodec<Object> jsonStringUnionCodec = new JSONStringUnionCodec(schema, type, this);
+        return jsonStringUnionCodec;
+    }
 }
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LazyJSONCodecFactory.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LazyJSONCodecFactory.java
new file mode 100644 (file)
index 0000000..74f7948
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017 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.yang.data.codec.gson;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
+
+/**
+ * Lazily-computed JSONCodecFactory. This is a non-thread-safe factory, which performs caching of codecs. It is most
+ * appropriate for one-off encodings of repetetive data.
+ *
+ * @author Robert Varga
+ */
+@NotThreadSafe
+final class LazyJSONCodecFactory extends JSONCodecFactory {
+    private final Map<TypedSchemaNode, JSONCodec<?>> codecs = new IdentityHashMap<>();
+
+    LazyJSONCodecFactory(final SchemaContext context) {
+        super(context);
+    }
+
+    @Override
+    JSONCodec<?> codecFor(final TypedSchemaNode schema) {
+        return codecs.computeIfAbsent(schema, node -> createCodec(schema, schema.getType()));
+    }
+
+    // Used by EagerJSONCodecFactory. The map is leaked as-is, as we do not expect it to be touched again.
+    Map<TypedSchemaNode, JSONCodec<?>> getCodecs() {
+        return codecs;
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SharedJSONCodecFactory.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SharedJSONCodecFactory.java
new file mode 100644 (file)
index 0000000..8cf4058
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 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.yang.data.codec.gson;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
+
+/**
+ * A thread-safe lazily-populated codec factory. Instances are cached in an internal weak/soft cache.
+ *
+ * @author Robert Varga
+ */
+@ThreadSafe
+final class SharedJSONCodecFactory extends JSONCodecFactory {
+    // Weak keys to retire the entry when SchemaContext goes away and to force identity-based lookup
+    private static final LoadingCache<SchemaContext, SharedJSONCodecFactory> CACHE = CacheBuilder.newBuilder()
+            .weakKeys().build(new CacheLoader<SchemaContext, SharedJSONCodecFactory>() {
+                @Override
+                public SharedJSONCodecFactory load(final SchemaContext key) {
+                    return new SharedJSONCodecFactory(key);
+                }
+            });
+
+    // Soft values to keep unreferenced codecs around for a bit, but eventually we want them to go away
+    private final LoadingCache<TypedSchemaNode, JSONCodec<?>> codecs = CacheBuilder.newBuilder().softValues()
+            .build(new CacheLoader<TypedSchemaNode, JSONCodec<?>>() {
+                @Override
+                public JSONCodec<?> load(@Nonnull final TypedSchemaNode key) {
+                    return createCodec(key, key.getType());
+                }
+            });
+
+    SharedJSONCodecFactory(final SchemaContext context) {
+        super(context);
+    }
+
+    static SharedJSONCodecFactory getIfPresent(final SchemaContext context) {
+        return CACHE.getIfPresent(context);
+    }
+
+    static SharedJSONCodecFactory get(final SchemaContext context) {
+        return CACHE.getUnchecked(context);
+    }
+
+    @Override
+    JSONCodec<?> codecFor(final TypedSchemaNode schema) {
+        return codecs.getUnchecked(schema);
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleJSONCodecFactory.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleJSONCodecFactory.java
new file mode 100644 (file)
index 0000000..13ab0d2
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 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.yang.data.codec.gson;
+
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
+
+/**
+ * A simplistic factory, which does not perform any codec caching. Minimizes resident memory footprint at the expense
+ * of creating short-lived objects.
+ *
+ * @author Robert Varga
+ */
+@ThreadSafe
+final class SimpleJSONCodecFactory extends JSONCodecFactory {
+    SimpleJSONCodecFactory(final SchemaContext context) {
+        super(context);
+    }
+
+    @Override
+    JSONCodec<?> codecFor(final TypedSchemaNode schema) {
+        return createCodec(schema, schema.getType());
+    }
+}