<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ org.opendaylight.yangtools.objcache.impl;resolution:=optional,
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
import org.opendaylight.yangtools.objcache.impl.StaticObjectCacheBinder;
import org.opendaylight.yangtools.objcache.spi.IObjectCacheFactory;
+import org.opendaylight.yangtools.objcache.spi.NoopObjectCacheBinder;
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));
- }
+ private static IObjectCacheFactory FACTORY;
+
+ private static synchronized IObjectCacheFactory initialize() {
+ // Double-check under lock
+ if (FACTORY != null) {
+ return FACTORY;
+ }
+
+ IObjectCacheFactory f;
+ try {
+ f = StaticObjectCacheBinder.getInstance().getProductCacheFactory();
+ FACTORY = f;
+ } catch (NoClassDefFoundError e) {
+ f = NoopObjectCacheBinder.INSTANCE.getProductCacheFactory();
+ }
+
+ 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));
+ }
}
*/
package org.opendaylight.yangtools.objcache.spi;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
import org.opendaylight.yangtools.concepts.ProductAwareBuilder;
import org.opendaylight.yangtools.objcache.ObjectCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.base.FinalizableSoftReference;
import com.google.common.base.Preconditions;
* 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<Object> {
- 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<Object, Object> cache;
-
- protected AbstractObjectCache(final Cache<Object, Object> cache, final FinalizableReferenceQueue queue) {
- this.queue = Preconditions.checkNotNull(queue);
- this.cache = Preconditions.checkNotNull(cache);
- }
-
- private <T> 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 <B extends ProductAwareBuilder<P>, 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> 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;
- }
+ /**
+ * 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.
+ */
+ @VisibleForTesting
+ 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.
+ */
+ protected abstract static class SoftKey<T> extends FinalizableSoftReference<T> {
+ private final int hashCode;
+
+ public SoftKey(final T referent, final FinalizableReferenceQueue q) {
+ super(Preconditions.checkNotNull(referent), q);
+ hashCode = referent.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ // 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<SoftKey<?>, Object> cache;
+
+ protected AbstractObjectCache(final Cache<SoftKey<?>, Object> cache, final FinalizableReferenceQueue queue) {
+ this.queue = Preconditions.checkNotNull(queue);
+ this.cache = Preconditions.checkNotNull(cache);
+ }
+
+ protected <T> SoftKey<T> createSoftKey(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.
+ */
+ return new SoftKey<T>(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 {}", this);
+ cache.invalidate(this);
+ }
+ };
+ }
+
+ @Override
+ public final <B extends ProductAwareBuilder<P>, P> P getProduct(final B builder) {
+ throw new UnsupportedOperationException();
+// 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
+ @SuppressWarnings("unchecked")
+ public final <T> T getReference(final T object) {
+ LOG.debug("Looking up reference for {}", object);
+ if (object == null) {
+ return null;
+ }
+
+ final SoftKey<T> key = createSoftKey(object);
+ try {
+ return (T) cache.get(key, new Callable<T>() {
+ @Override
+ public T call() {
+ return object;
+ }
+ });
+ } catch (ExecutionException e) {
+ throw new IllegalStateException("Failed to load value", e);
+ }
+ }
}
--- /dev/null
+/*
+ * 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.objcache.ObjectCache;
+
+public final class NoopObjectCacheBinder extends AbstractObjectCacheBinder {
+ public static final NoopObjectCacheBinder INSTANCE = new NoopObjectCacheBinder();
+
+ private NoopObjectCacheBinder() {
+ super(new IObjectCacheFactory() {
+ @Override
+ public ObjectCache getObjectCache(final Class<?> objClass) {
+ return NoopObjectCache.getInstance();
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.objcache.ObjectCache;
+import org.opendaylight.yangtools.objcache.ObjectCacheFactory;
+
+public class CacheFactoryTest {
+
+ @Test
+ public void testInvalidEnvironment() {
+ final ObjectCache oc = ObjectCacheFactory.getObjectCache(String.class);
+
+ assertNotNull(oc);
+ assertEquals(NoopObjectCache.class, oc.getClass());
+ }
+}
--- /dev/null
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.objcache.ObjectCache;
+import org.opendaylight.yangtools.objcache.spi.AbstractObjectCache.SoftKey;
+
+import com.google.common.base.FinalizableReferenceQueue;
+import com.google.common.cache.CacheBuilder;
+
+public class CacheTest {
+ private FinalizableReferenceQueue queue;
+ private ObjectCache oc;
+
+ @Before
+ public void setUp() {
+ queue = new FinalizableReferenceQueue();
+ oc = new AbstractObjectCache(CacheBuilder.newBuilder().softValues().<SoftKey<?>, Object>build(), queue) {
+ };
+ }
+
+ @After
+ public void tearDown() {
+ queue.close();
+ }
+
+ @Test
+ public void testMissingKey() {
+ final String key1 = "abcd";
+ final String key2 = "efgh";
+
+ assertSame(key1, oc.getReference(key1));
+ assertSame(key2, oc.getReference(key2));
+ }
+
+ @Test
+ public void testPresentKey() {
+ final String key1 = new String("abcd");
+ final String key2 = new String("abcd");
+
+ assertSame(key1, oc.getReference(key1));
+
+ final String key3 = oc.getReference(key2);
+ assertEquals(key2, key3);
+ assertNotSame(key2, key3);
+ assertSame(key1, key3);
+ }
+}
--- /dev/null
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.objcache.spi.AbstractObjectCache.SoftKey;
+
+import com.google.common.base.FinalizableReferenceQueue;
+
+public class SoftKeyTest {
+ private FinalizableReferenceQueue queue;
+
+
+ @Before
+ public void setUp() {
+ queue = new FinalizableReferenceQueue();
+ }
+
+ @After
+ public void tearDown() {
+ queue.close();
+ }
+
+ @Test
+ public void testEquals() {
+ final String str = "foo";
+
+ final SoftKey<?> key = new SoftKey<String>(str, queue) {
+ @Override
+ public void finalizeReferent() {
+
+ }
+ };
+
+ assertSame(str, key.get());
+ assertEquals(str.hashCode(), key.hashCode());
+ assertTrue(key.equals(str));
+ key.clear();
+ assertNull(key.get());
+ assertEquals(str.hashCode(), key.hashCode());
+ assertFalse(key.equals(str));
+ }
+}
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.opendaylight.yangtools.objcache.impl
+ </Export-Package>
+ <Private-Package>
+ org.opendaylight.yangtools.objcache.guava
+ </Private-Package>
+ </instructions>
+ </configuration>
</plugin>
</plugins>
</build>
import com.google.common.cache.CacheBuilderSpec;
final class GuavaObjectCache extends AbstractObjectCache {
- public GuavaObjectCache(final FinalizableReferenceQueue queue, final CacheBuilderSpec spec) {
- super(CacheBuilder.from(spec).softValues().build(), queue);
- }
+ public GuavaObjectCache(final FinalizableReferenceQueue queue) {
+ super(CacheBuilder.newBuilder().softValues().<SoftKey<?>, Object>build(), queue);
+ }
+
+ public GuavaObjectCache(final FinalizableReferenceQueue queue, final CacheBuilderSpec spec) {
+ super(CacheBuilder.from(spec).<SoftKey<?>, Object>build(), queue);
+ }
}
import com.google.common.base.FinalizableReferenceQueue;
-public final class GuavaObjectCacheFactory implements IObjectCacheFactory {
- private static final GuavaObjectCacheFactory INSTANCE = new GuavaObjectCacheFactory();
- private final FinalizableReferenceQueue queue = new FinalizableReferenceQueue();
- private final ObjectCache cache;
+public final class GuavaObjectCacheFactory implements AutoCloseable, IObjectCacheFactory {
+ private static final GuavaObjectCacheFactory INSTANCE = new GuavaObjectCacheFactory();
+ private final FinalizableReferenceQueue queue = new FinalizableReferenceQueue();
+ private final ObjectCache cache;
- private GuavaObjectCacheFactory() {
- // FIXME: make this more dynamic
- this.cache = new GuavaObjectCache(queue, null);
- }
+ private GuavaObjectCacheFactory() {
+ // FIXME: make this more dynamic using a spec
+ this.cache = new GuavaObjectCache(queue);
+ }
- @Override
- public void finalize() throws Throwable {
- try {
- queue.close();
- } finally {
- super.finalize();
- }
- }
+ @Override
+ public ObjectCache getObjectCache(final Class<?> objClass) {
+ return cache;
+ }
- @Override
- public ObjectCache getObjectCache(final Class<?> objClass) {
- return cache;
- }
+ @Override
+ public void close() {
+ queue.close();
+ }
- public static GuavaObjectCacheFactory getInstance() {
- return INSTANCE;
- }
+ public static GuavaObjectCacheFactory getInstance() {
+ return INSTANCE;
+ }
}
--- /dev/null
+/*
+ * 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.guava;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.objcache.ObjectCache;
+import org.opendaylight.yangtools.objcache.ObjectCacheFactory;
+
+public class GuavaObjectCacheTest {
+ private ObjectCache cache;
+
+ @Before
+ public void setUp() {
+ cache = ObjectCacheFactory.getObjectCache(String.class);
+ }
+
+ @Test
+ public void testCorrectWiring() {
+ assertEquals(GuavaObjectCache.class, cache.getClass());
+ }
+
+ @Test
+ public void testInitialReference() {
+ final String s1 = "abcd";
+ final String s2 = cache.getReference(s1);
+ assertSame(s1, s2);
+ }
+
+ @Test
+ public void testMultipleReferences() {
+ final String s1 = "abcd";
+ final String s2 = new String(s1);
+
+ // Preliminary check
+ assertEquals(s1, s2);
+ assertNotSame(s1, s2);
+
+ assertSame(s1, cache.getReference(s1));
+ assertSame(s1, cache.getReference(s2));
+ assertNotSame(s2, cache.getReference(s2));
+ }
+
+}
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.opendaylight.yangtools.objcache.impl
+ </Export-Package>
+ <Private-Package>
+ org.opendaylight.yangtools.objcache.noop
+ </Private-Package>
+ </instructions>
+ </configuration>
</plugin>
</plugins>
</build>
import org.opendaylight.yangtools.objcache.spi.NoopObjectCache;
public final class StaticObjectCacheBinder extends AbstractObjectCacheBinder {
- private static final StaticObjectCacheBinder INSTANCE = new StaticObjectCacheBinder();
+ private static final StaticObjectCacheBinder INSTANCE = new StaticObjectCacheBinder();
- private StaticObjectCacheBinder() {
- super(new IObjectCacheFactory() {
- @Override
- public ObjectCache getObjectCache(final Class<?> objClass) {
- return NoopObjectCache.getInstance();
- }
- });
- }
+ private StaticObjectCacheBinder() {
+ super(new IObjectCacheFactory() {
+ @Override
+ public ObjectCache getObjectCache(final Class<?> objClass) {
+ return NoopObjectCache.getInstance();
+ }
+ });
+ }
- public static StaticObjectCacheBinder getInstance() {
- return INSTANCE;
- }
+ public static StaticObjectCacheBinder getInstance() {
+ return INSTANCE;
+ }
}
<artifactId>concepts</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>object-cache-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>object-cache-guava</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>object-cache-noop</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>