diff --git a/owner/src/main/java/org/aeonbits/owner/ConfigCache.java b/owner/src/main/java/org/aeonbits/owner/ConfigCache.java new file mode 100644 index 00000000..fea352bd --- /dev/null +++ b/owner/src/main/java/org/aeonbits/owner/ConfigCache.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2013-2014, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package org.aeonbits.owner; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Utility class to have a cache singleton for Config instances. + * + * @author Luigi R. Viggiano + * @since 1.0.6 + */ +public final class ConfigCache { + private static ConcurrentMap CACHE = new ConcurrentHashMap(); + + /** Don't let anyone instantiate this class */ + private ConfigCache() {} + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * The factory used to create new instances is the static {@link ConfigFactory#INSTANCE}. + * + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, which maps methods + * to property values. + */ + public static T getOrCreate(Class clazz, Map... imports) { + return getOrCreate(ConfigFactory.INSTANCE, clazz, clazz, imports); + } + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * + * @param factory the factory to use to eventually create the instance. + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, which maps methods + * to property values. + */ + public static T getOrCreate(Factory factory, Class clazz, Map... imports) { + return getOrCreate(factory, clazz, clazz, imports); + } + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * + * @param key the key object to be used to identify the instance in the cache. + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, which maps methods + * to property values. + */ + public static T getOrCreate(Object key, Class clazz, Map... imports) { + return getOrCreate(ConfigFactory.INSTANCE, key, clazz, imports); + } + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * + * @param factory the factory to use to eventually create the instance. + * @param key the key object to be used to identify the instance in the cache. + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, which maps methods + * to property values. + */ + public static T getOrCreate(Factory factory, Object key, + Class clazz, Map[] imports) { + T existing = get(key); + if (existing != null) return existing; + T created = factory.create(clazz, imports); + T raced = add(key, created); + return raced != null ? raced : created; + } + + /** + * Gets from the cache the {@link Config} instance identified by the given key. + * + * @param key the key object to be used to identify the instance in the cache. + * @param type of the interface. + * @return the {@link Config} object from the cache if exists, or null if it doesn't. + */ + public static T get(Object key) { + return (T) CACHE.get(key); + } + + /** + * Adds a {@link Config} object into the cache. + * + * @param key the key object to be used to identify the instance in the cache. + * @param instance the instance of the {@link Config} object to be stored into the cache. + * @param type of the interface. + * @return the previous value associated with the specified key, or + * null if there was no mapping for the key. + */ + public static T add(Object key, T instance) { + return (T) CACHE.putIfAbsent(key, instance); + } + + /** + * Removes all of the cached instances. + * The cache will be empty after this call returns. + */ + public static void clear() { + CACHE.clear(); + } + + /** + * Removes the cached instance for the given key if it is present. + * + *

Returns previous instance associated to the given key in the cache, + * or null if the cache contained no instance for the given key. + * + *

The cache will not contain the instance for the specified key once the + * call returns. + * + * @param key key whose instance is to be removed from the cache. + * @return the previous instance associated with key, or + * null if there was no instance for key. + */ + public static T remove(Object key) { + return (T) CACHE.remove(key); + } +} diff --git a/owner/src/test/java/org/aeonbits/owner/cache/CacheConfigTest.java b/owner/src/test/java/org/aeonbits/owner/cache/CacheConfigTest.java new file mode 100644 index 00000000..e34ec6cf --- /dev/null +++ b/owner/src/test/java/org/aeonbits/owner/cache/CacheConfigTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2013-2014, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package org.aeonbits.owner.cache; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.ConfigCache; +import org.aeonbits.owner.ConfigFactory; +import org.aeonbits.owner.Factory; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Matchers; + +import java.util.Map; + +import static junit.framework.Assert.assertNull; +import static org.junit.Assert.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +/** + * @author Luigi R. Viggiano + */ +public class CacheConfigTest { + + static interface MyConfig extends Config {} + + @Before + public void before() { + ConfigCache.clear(); + } + + @Test + public void testGetOrCreateFromCache() { + MyConfig first = ConfigCache.getOrCreate(MyConfig.class); + MyConfig second = ConfigCache.getOrCreate(MyConfig.class); + assertSame(first, second); + } + + @Test + public void testGetOrCreateWithFactory() { + Factory factory = ConfigFactory.newInstance(); + Factory spy = spy(factory); + MyConfig first = ConfigCache.getOrCreate(spy, MyConfig.class); + MyConfig second = ConfigCache.getOrCreate(spy, MyConfig.class); + MyConfig third = ConfigCache.getOrCreate(spy, MyConfig.class); + assertSame(first, second); + assertSame(second, third); + verify(spy, times(1)).create(eq(MyConfig.class), Matchers.[]>anyVararg()); + } + + @Test + public void testGetOrCreateUsingNameKey() { + MyConfig first = ConfigCache.getOrCreate("MyConfig", MyConfig.class); + MyConfig second = ConfigCache.getOrCreate("MyConfig", MyConfig.class); + assertSame(first, second); + } + + @Test + public void testRemoveUsingClassAsKey() { + MyConfig first = ConfigCache.getOrCreate(MyConfig.class); + Config removed = ConfigCache.remove(MyConfig.class); + assertNotNull(removed); + + MyConfig second = ConfigCache.getOrCreate(MyConfig.class); + MyConfig third = ConfigCache.getOrCreate(MyConfig.class); + + assertNotSame(first, second); + assertSame(second, third); + } + + @Test + public void testRemoveUsingNameKey() { + MyConfig first = ConfigCache.getOrCreate("foo", MyConfig.class); + + Config removed = ConfigCache.remove("foo"); + assertNotNull(removed); + + MyConfig second = ConfigCache.getOrCreate("foo", MyConfig.class); + MyConfig third = ConfigCache.getOrCreate("foo", MyConfig.class); + + assertNotSame(first, second); + assertSame(second, third); + } + + @Test + public void testRemove() { + MyConfig first = ConfigCache.getOrCreate("MyConfig", MyConfig.class); + Config removed = ConfigCache.remove("MyConfig"); + MyConfig second = ConfigCache.getOrCreate("MyConfig", MyConfig.class); + assertNotSame(first, second); + assertSame(first, removed); + } + + @Test + public void testClear() { + MyConfig first = ConfigCache.getOrCreate("MyConfig", MyConfig.class); + ConfigCache.clear(); + MyConfig second = ConfigCache.getOrCreate("MyConfig", MyConfig.class); + assertNotSame(first, second); + } + + @Test + public void testGetWhenInstanceDoesNotExists() { + MyConfig instance = ConfigCache.get(MyConfig.class); + assertNull(instance); + } + + @Test + public void testGetWhenInstanceExists() { + MyConfig created = ConfigCache.getOrCreate(MyConfig.class); + MyConfig got = ConfigCache.get(MyConfig.class); + assertSame(created, got); + } + + @Test + public void testAdd() { + MyConfig dummy = ConfigFactory.create(MyConfig.class); + MyConfig previous = ConfigCache.add("foo", dummy); + MyConfig cached = ConfigCache.getOrCreate("foo", MyConfig.class); + + assertNull(previous); + assertSame(dummy, cached); + } + +}