From e5b2e5891368d3c99a484e397997dc9cf138da83 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Mon, 24 Mar 2014 20:32:00 +0100 Subject: [PATCH] Introduce Object Cache API and no-op implementation This introduces a statically-bound cache for objets. This is primarily useful for implementing CPU/memory tradeoffs for limiting duplicate instances of common objects. Change-Id: I6081dc121c8cc3defd029fe4da1e29fdabea68d0 Signed-off-by: Robert Varga --- common/object-cache-api/pom.xml | 68 ++++++++ .../yangtools/objcache/ObjectCache.java | 50 ++++++ .../objcache/ObjectCacheFactory.java | 53 ++++++ .../impl/StaticObjectCacheBinder.java | 24 +++ .../yangtools/objcache/impl/package-info.java | 12 ++ .../yangtools/objcache/package-info.java | 11 ++ .../objcache/spi/AbstractObjectCache.java | 156 ++++++++++++++++++ .../spi/AbstractObjectCacheBinder.java | 25 +++ .../objcache/spi/IObjectCacheFactory.java | 16 ++ .../objcache/spi/NoopObjectCache.java | 43 +++++ .../spi/ObjectCacheFactoryBinder.java | 20 +++ .../yangtools/objcache/spi/package-info.java | 12 ++ common/object-cache-noop/pom.xml | 35 ++++ .../impl/StaticObjectCacheBinder.java | 30 ++++ common/pom.xml | 17 ++ pom.xml | 5 + 16 files changed, 577 insertions(+) create mode 100644 common/object-cache-api/pom.xml create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCache.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCacheFactory.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/package-info.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/package-info.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCacheBinder.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/IObjectCacheFactory.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/NoopObjectCache.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/ObjectCacheFactoryBinder.java create mode 100644 common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/package-info.java create mode 100644 common/object-cache-noop/pom.xml create mode 100644 common/object-cache-noop/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java diff --git a/common/object-cache-api/pom.xml b/common/object-cache-api/pom.xml new file mode 100644 index 0000000000..8ef8a14433 --- /dev/null +++ b/common/object-cache-api/pom.xml @@ -0,0 +1,68 @@ + + + + + + + org.opendaylight.yangtools + common-parent + 0.6.2-SNAPSHOT + + bundle + 4.0.0 + object-cache-api + + + + org.opendaylight.yangtools + concepts + + + org.slf4j + slf4j-api + + + com.google.guava + guava + + + com.google.code.findbugs + jsr305 + provided + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.apache.maven.plugins + maven-antrun-plugin + + + process-classes + + run + + + + + + + + + + + + + diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCache.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCache.java new file mode 100644 index 0000000000..9f3276837d --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCache.java @@ -0,0 +1,50 @@ +/* + * 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.objcache; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.concepts.ProductAwareBuilder; + +/** + * A cache of objects. Caches are useful for reducing memory overhead + * stemming from multiple copies of identical objects -- by putting + * a cache in the instantiation path, one can expend some memory on + * indexes and spend some CPU cycles on walking the index to potentially + * end up with a reused object. + * + * Note that the cached objects should really be semantically {@link Immutable}. + * This interface does not enforce that interface contract simply because + * there are third-party objects which fulfill this contract. + */ +public interface ObjectCache { + /** + * Get a reference for an object which is equal to specified object. + * The cache is free return either a cached instance, or retain the + * object and return it back. + * + * @param object Requested object, may be null + * @return Reference to an object which is equal to the one passed in. + * If @object was @null, this method returns @null. + */ + T getReference(@Nullable T object); + + /** + * Get a reference to an object equal to the product of a builder. + * The builder is expected to remain constant while this method + * executes. Unlike {@link #getReference(Object)}, this method has + * the potential of completely eliding the product instantiation. + * + * @param builder Builder instance, may not be null + * @return Result of builder's toInstance() product, or an equal + * object. + */ + , P> P getProduct(@Nonnull B builder); +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCacheFactory.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCacheFactory.java new file mode 100644 index 0000000000..58ca11c4d2 --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/ObjectCacheFactory.java @@ -0,0 +1,53 @@ +/* + * 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.objcache; + +import javax.annotation.Nonnull; + +import org.opendaylight.yangtools.objcache.impl.StaticObjectCacheBinder; +import org.opendaylight.yangtools.objcache.spi.IObjectCacheFactory; + +import com.google.common.base.Preconditions; + +/** + * Point of entry for acquiring an {@link ObjectCache} instance. + */ +public final class ObjectCacheFactory { + private static IObjectCacheFactory FACTORY; + + private static synchronized IObjectCacheFactory initialize() { + // Double-check under lock + if (FACTORY != null) { + return FACTORY; + } + + final IObjectCacheFactory f = StaticObjectCacheBinder.getInstance().getProductCacheFactory(); + FACTORY = f; + return f; + } + + public static synchronized void reset() { + FACTORY = null; + } + + /** + * Get an ObjectCache for caching a particular object class. Note + * that it may be shared for multiple classes. + * + * @param objClass Class of objects which are to be cached + * @return Object cache instance. + */ + public static ObjectCache getObjectCache(@Nonnull final Class objClass) { + IObjectCacheFactory f = FACTORY; + if (f == null) { + f = initialize(); + } + + return f.getObjectCache(Preconditions.checkNotNull(objClass)); + } +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java new file mode 100644 index 0000000000..59cd30f05a --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java @@ -0,0 +1,24 @@ +/* + * 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.objcache.impl; + +import org.opendaylight.yangtools.objcache.spi.AbstractObjectCacheBinder; + +/* + * This is a dummy placeholder implementation. The API package is bound to + * it at compile-time, but it is not packaged and thus not present at run-time. + */ +public final class StaticObjectCacheBinder extends AbstractObjectCacheBinder { + private StaticObjectCacheBinder() { + super(null); + } + + public static StaticObjectCacheBinder getInstance() { + throw new IllegalStateException("This class should have been replaced"); + } +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/package-info.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/package-info.java new file mode 100644 index 0000000000..ecfa94a1f4 --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/impl/package-info.java @@ -0,0 +1,12 @@ +/* + * 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 + */ +/** + * Static binding implementation package. The API package is bound at compile-time + * to this package. Implementations are expected to supply this package. + */ +package org.opendaylight.yangtools.objcache.impl; \ No newline at end of file diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/package-info.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/package-info.java new file mode 100644 index 0000000000..a28685af52 --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/package-info.java @@ -0,0 +1,11 @@ +/* + * 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.objcache; \ No newline at end of file diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java new file mode 100644 index 0000000000..963030cf8b --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java @@ -0,0 +1,156 @@ +/* + * 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.objcache.spi; + +import org.opendaylight.yangtools.concepts.ProductAwareBuilder; +import org.opendaylight.yangtools.objcache.ObjectCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.FinalizableReferenceQueue; +import com.google.common.base.FinalizableSoftReference; +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; + +/** + * Abstract object cache implementation. This implementation takes care + * of interacting with the user and manages interaction with the Garbage + * Collector (via soft references). Subclasses are expected to provide + * a backing {@link Cache} instance and provide the + */ +public abstract class AbstractObjectCache implements ObjectCache { + /** + * Key used when looking up a ProductAwareBuilder product. We assume + * the builder is not being modified for the duration of the lookup, + * anything else is the user's fault. + */ + private static final class BuilderKey { + private final ProductAwareBuilder builder; + + private BuilderKey(final ProductAwareBuilder builder) { + this.builder = Preconditions.checkNotNull(builder); + } + + @Override + public int hashCode() { + return builder.productHashCode(); + } + + @Override + public boolean equals(Object obj) { + /* + * We can tolerate null objects coming our way, but we need + * to be on the lookout for WeakKeys, as we cannot pass them + * directly to productEquals(). + */ + if (obj != null && obj instanceof SoftKey) { + obj = ((SoftKey)obj).get(); + } + + return builder.productEquals(obj); + } + } + + /** + * Key used in the underlying map. It is essentially a soft reference, with + * slightly special properties. + * + * It acts as a proxy for the object it refers to and essentially delegates + * to it. There are three exceptions here: + * + * 1) This key needs to have a cached hash code. The requirement is that the + * key needs to be able to look itself up after the reference to the object + * has been cleared (and thus we can no longer look it up from there). One + * typical container where we are stored are HashMaps -- and they need it + * to be constant. + * 2) This key does not tolerate checks to see if its equal to null. While we + * could return false, we want to catch offenders who try to store nulls + * in the cache. + * 3) This key inverts the check for equality, e.g. it calls equals() on the + * object which was passed to its equals(). Instead of supplying itself, + * it supplies the referent. If the soft reference is cleared, such check + * will return false, which is fine as it prevents normal lookup from + * seeing the cleared key. Removal is handled by the explicit identity + * check. + */ + private static abstract class SoftKey extends FinalizableSoftReference { + private final int hashCode; + + public SoftKey(final Object referent, final FinalizableReferenceQueue q) { + super(Preconditions.checkNotNull(referent), q); + hashCode = referent.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + Preconditions.checkState(obj != null); + + // Order is important: we do not want to call equals() on ourselves! + return this == obj || obj.equals(get()); + } + + @Override + public int hashCode() { + return hashCode; + } + } + + private static final Logger LOG = LoggerFactory.getLogger(AbstractObjectCache.class); + private final FinalizableReferenceQueue queue; + private final Cache cache; + + protected AbstractObjectCache(final Cache cache, final FinalizableReferenceQueue queue) { + this.queue = Preconditions.checkNotNull(queue); + this.cache = Preconditions.checkNotNull(cache); + } + + private T put(final T object) { + /* + * This may look like a race (having a soft reference and not have + * it in the cache). In fact this is protected by the fact we still + * have a strong reference on the object in our arguments and that + * reference survives past method return since we return it. + */ + final Object key = new SoftKey(object, queue) { + @Override + public void finalizeReferent() { + /* + * NOTE: while it may be tempting to add "object" into this + * trace message, do not ever do that: it would retain + * a strong reference, preventing collection. + */ + LOG.trace("Invalidating key {} for object {}", this); + cache.invalidate(this); + } + }; + cache.put(key, object); + LOG.debug("Cached key {} to object {}", key, object); + return object; + } + + @Override + public final , P> P getProduct(final B builder) { + LOG.debug("Looking up product for {}", builder); + + @SuppressWarnings("unchecked") + final P ret = (P) cache.getIfPresent(new BuilderKey(builder)); + return ret == null ? put(Preconditions.checkNotNull(builder.toInstance())) : ret; + } + + @Override + public final T getReference(final T object) { + LOG.debug("Looking up reference for {}", object); + if (object == null) { + return null; + } + + @SuppressWarnings("unchecked") + final T ret = (T) cache.getIfPresent(object); + return ret == null ? put(object) : ret; + } +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCacheBinder.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCacheBinder.java new file mode 100644 index 0000000000..be860eadcf --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCacheBinder.java @@ -0,0 +1,25 @@ +/* + * 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.objcache.spi; + +import javax.annotation.Nonnull; + +import com.google.common.base.Preconditions; + +public abstract class AbstractObjectCacheBinder implements ObjectCacheFactoryBinder { + private final IObjectCacheFactory factory; + + protected AbstractObjectCacheBinder(@Nonnull final IObjectCacheFactory factory) { + this.factory = Preconditions.checkNotNull(factory); + } + + @Override + public final IObjectCacheFactory getProductCacheFactory() { + return factory; + } +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/IObjectCacheFactory.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/IObjectCacheFactory.java new file mode 100644 index 0000000000..7ee6bc411f --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/IObjectCacheFactory.java @@ -0,0 +1,16 @@ +/* + * 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.objcache.spi; + +import javax.annotation.Nonnull; + +import org.opendaylight.yangtools.objcache.ObjectCache; + +public interface IObjectCacheFactory { + ObjectCache getObjectCache(@Nonnull Class objClass); +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/NoopObjectCache.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/NoopObjectCache.java new file mode 100644 index 0000000000..11631993da --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/NoopObjectCache.java @@ -0,0 +1,43 @@ +/* + * 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.objcache.spi; + +import org.opendaylight.yangtools.concepts.ProductAwareBuilder; +import org.opendaylight.yangtools.objcache.ObjectCache; + +/** + * No-operation implementation of an Object Cache. This implementation + * does not do any caching, so it only returns the request object. + */ +public final class NoopObjectCache implements ObjectCache { + private static final NoopObjectCache INSTANCE = new NoopObjectCache(); + + private NoopObjectCache() { + + } + + /** + * Get the cache instance. Since the cache does not have any state, + * this method always returns a singleton instance. + * + * @return Cache instance. + */ + public static NoopObjectCache getInstance() { + return INSTANCE; + } + + @Override + public T getReference(final T object) { + return object; + } + + @Override + public , P> P getProduct(final B builder) { + return builder.toInstance(); + } +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/ObjectCacheFactoryBinder.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/ObjectCacheFactoryBinder.java new file mode 100644 index 0000000000..012428c044 --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/ObjectCacheFactoryBinder.java @@ -0,0 +1,20 @@ +/* + * 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.objcache.spi; + +/** + * Interface binding an implementation into ObjectCacheFactory. + */ +public interface ObjectCacheFactoryBinder { + /** + * Get the implementation-specific cache factory. + * + * @return Implementation-specific factory. + */ + IObjectCacheFactory getProductCacheFactory(); +} diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/package-info.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/package-info.java new file mode 100644 index 0000000000..57223b6cce --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/package-info.java @@ -0,0 +1,12 @@ +/* + * 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 + */ +/** + * Service Provider Interface for Object Cache. Object cache implementations + * use classes contained in this package to implement their functionality. + */ +package org.opendaylight.yangtools.objcache.spi; \ No newline at end of file diff --git a/common/object-cache-noop/pom.xml b/common/object-cache-noop/pom.xml new file mode 100644 index 0000000000..e2c9bce995 --- /dev/null +++ b/common/object-cache-noop/pom.xml @@ -0,0 +1,35 @@ + + + + + + + org.opendaylight.yangtools + common-parent + 0.6.2-SNAPSHOT + + bundle + 4.0.0 + object-cache-noop + + + + ${project.groupId} + object-cache-api + + + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/common/object-cache-noop/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java b/common/object-cache-noop/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.java new file mode 100644 index 0000000000..2af58bf5c3 --- /dev/null +++ b/common/object-cache-noop/src/main/java/org/opendaylight/yangtools/objcache/impl/StaticObjectCacheBinder.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.objcache.impl; + +import org.opendaylight.yangtools.objcache.ObjectCache; +import org.opendaylight.yangtools.objcache.spi.AbstractObjectCacheBinder; +import org.opendaylight.yangtools.objcache.spi.IObjectCacheFactory; +import org.opendaylight.yangtools.objcache.spi.NoopObjectCache; + +public final class StaticObjectCacheBinder extends AbstractObjectCacheBinder { + private static final StaticObjectCacheBinder INSTANCE = new StaticObjectCacheBinder(); + + private StaticObjectCacheBinder() { + super(new IObjectCacheFactory() { + @Override + public ObjectCache getObjectCache(final Class objClass) { + return NoopObjectCache.getInstance(); + } + }); + } + + public static StaticObjectCacheBinder getInstance() { + return INSTANCE; + } +} diff --git a/common/pom.xml b/common/pom.xml index 10db456f55..ccf50baebd 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -22,6 +22,23 @@ concepts mockito-configuration + object-cache-api + object-cache-noop + + + + ${project.groupId} + object-cache-api + ${project.version} + + + ${project.groupId} + object-cache-noop + ${project.version} + + + + diff --git a/pom.xml b/pom.xml index 2d39835831..e229268818 100644 --- a/pom.xml +++ b/pom.xml @@ -171,6 +171,11 @@ commons-lang3 ${commons.lang.version} + + com.google.code.findbugs + jsr305 + 2.0.3 + -- 2.36.6