Skip to content

Commit

Permalink
Created ConfigCache implementation, that works as a Singleton.
Browse files Browse the repository at this point in the history
See issue #64
  • Loading branch information
Luigi R. Viggiano committed Apr 8, 2014
1 parent 08d041a commit 9b521d4
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 0 deletions.
137 changes: 137 additions & 0 deletions owner/src/main/java/org/aeonbits/owner/ConfigCache.java
Original file line number Diff line number Diff line change
@@ -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<Object, Config> CACHE = new ConcurrentHashMap<Object, Config>();

/** 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 <T> 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 extends Config> T getOrCreate(Class<? extends T> 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 <T> 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 extends Config> T getOrCreate(Factory factory, Class<? extends T> 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 <T> 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 extends Config> T getOrCreate(Object key, Class<? extends T> 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 <T> 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 extends Config> T getOrCreate(Factory factory, Object key,
Class<? extends T> 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 <T> type of the interface.
* @return the {@link Config} object from the cache if exists, or <tt>null</tt> if it doesn't.
*/
public static <T extends Config> 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 <T> type of the interface.
* @return the previous value associated with the specified key, or
* <tt>null</tt> if there was no mapping for the key.
*/
public static <T extends Config> 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.
*
* <p>Returns previous instance associated to the given key in the cache,
* or <tt>null</tt> if the cache contained no instance for the given key.
*
* <p>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 <tt>key</tt>, or
* <tt>null</tt> if there was no instance for <tt>key</tt>.
*/
public static <T extends Config> T remove(Object key) {
return (T) CACHE.remove(key);
}
}
131 changes: 131 additions & 0 deletions owner/src/test/java/org/aeonbits/owner/cache/CacheConfigTest.java
Original file line number Diff line number Diff line change
@@ -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.<Map<?, ?>[]>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);
}

}

0 comments on commit 9b521d4

Please sign in to comment.