[LANG-678] Added support for ConcurrentMap.putIfAbsent() to ConcurrentUtils.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1071401 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oliver Heger 2011-02-16 21:20:27 +00:00
parent 536ea90fc0
commit 3c286d89d0
2 changed files with 240 additions and 0 deletions

View File

@ -16,6 +16,7 @@
*/
package org.apache.commons.lang3.concurrent;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -204,6 +205,105 @@ public static <T> T initializeUnchecked(ConcurrentInitializer<T> initializer) {
}
}
//-----------------------------------------------------------------------
/**
* <p>
* Puts a value in the specified {@code ConcurrentMap} if the key is not yet
* present. This method works similar to the {@code putIfAbsent()} method of
* the {@code ConcurrentMap} interface, but the value returned is different.
* Basically, this method is equivalent to the following code fragment:
*
* <pre>
* if (!map.containsKey(key)) {
* map.put(key, value);
* return value;
* } else {
* return map.get(key);
* }
* </pre>
*
* except that the action is performed atomically. So this method always
* returns the value which is stored in the map.
* </p>
* <p>
* This method is <b>null</b>-safe: It accepts a <b>null</b> map as input
* without throwing an exception. In this case the return value is
* <b>null</b>, too.
* </p>
*
* @param <K> the type of the keys of the map
* @param <V> the type of the values of the map
* @param map the map to be modified
* @param key the key of the value to be added
* @param value the value to be added
* @return the value stored in the map after this operation
*/
public static <K, V> V putIfAbsent(ConcurrentMap<K, V> map, K key, V value) {
if (map == null) {
return null;
}
V result = map.putIfAbsent(key, value);
return (result != null) ? result : value;
}
/**
* Checks if a concurrent map contains a key and creates a corresponding
* value if not. This method first checks the presence of the key in the
* given map. If it is already contained, its value is returned. Otherwise
* the {@code get()} method of the passed in {@link ConcurrentInitializer}
* is called. With the resulting object
* {@link #putIfAbsent(ConcurrentMap, Object, Object)} is called. This
* handles the case that in the meantime another thread has added the key to
* the map. Both the map and the initializer can be <b>null</b>; in this
* case this method simply returns <b>null</b>.
*
* @param <K> the type of the keys of the map
* @param <V> the type of the values of the map
* @param map the map to be modified
* @param key the key of the value to be added
* @param init the {@link ConcurrentInitializer} for creating the value
* @return the value stored in the map after this operation; this may or may
* not be the object created by the {@link ConcurrentInitializer}
* @throws ConcurrentException if the initializer throws an exception
*/
public static <K, V> V createIfAbsent(ConcurrentMap<K, V> map, K key,
ConcurrentInitializer<V> init) throws ConcurrentException {
if (map == null || init == null) {
return null;
}
V value = map.get(key);
if (value == null) {
return putIfAbsent(map, key, init.get());
}
return value;
}
/**
* Checks if a concurrent map contains a key and creates a corresponding
* value if not, suppressing checked exceptions. This method calls
* {@code createIfAbsent()}. If a {@link ConcurrentException} is thrown, it
* is caught and re-thrown as a {@link ConcurrentRuntimeException}.
*
* @param <K> the type of the keys of the map
* @param <V> the type of the values of the map
* @param map the map to be modified
* @param key the key of the value to be added
* @param init the {@link ConcurrentInitializer} for creating the value
* @return the value stored in the map after this operation; this may or may
* not be the object created by the {@link ConcurrentInitializer}
* @throws ConcurrentRuntimeException if the initializer throws an exception
*/
public static <K, V> V createIfAbsentUnchecked(ConcurrentMap<K, V> map,
K key, ConcurrentInitializer<V> init) {
try {
return createIfAbsent(map, key, init);
} catch (ConcurrentException cex) {
throw new ConcurrentRuntimeException(cex.getCause());
}
}
//-----------------------------------------------------------------------
/**
* <p>

View File

@ -23,6 +23,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -405,4 +407,142 @@ public void testConstantFuture_null() throws Exception {
assertFalse(test.cancel(false));
}
//-----------------------------------------------------------------------
/**
* Tests putIfAbsent() if the map contains the key in question.
*/
@Test
public void testPutIfAbsentKeyPresent() {
final String key = "testKey";
final Integer value = 42;
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
map.put(key, value);
assertEquals("Wrong result", value,
ConcurrentUtils.putIfAbsent(map, key, 0));
assertEquals("Wrong value in map", value, map.get(key));
}
/**
* Tests putIfAbsent() if the map does not contain the key in question.
*/
@Test
public void testPutIfAbsentKeyNotPresent() {
final String key = "testKey";
final Integer value = 42;
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
assertEquals("Wrong result", value,
ConcurrentUtils.putIfAbsent(map, key, value));
assertEquals("Wrong value in map", value, map.get(key));
}
/**
* Tests putIfAbsent() if a null map is passed in.
*/
@Test
public void testPutIfAbsentNullMap() {
assertNull("Wrong result",
ConcurrentUtils.putIfAbsent(null, "test", 100));
}
/**
* Tests createIfAbsent() if the key is found in the map.
*/
@Test
public void testCreateIfAbsentKeyPresent() throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Integer> init = EasyMock
.createMock(ConcurrentInitializer.class);
EasyMock.replay(init);
final String key = "testKey";
final Integer value = 42;
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
map.put(key, value);
assertEquals("Wrong result", value,
ConcurrentUtils.createIfAbsent(map, key, init));
assertEquals("Wrong value in map", value, map.get(key));
EasyMock.verify(init);
}
/**
* Tests createIfAbsent() if the map does not contain the key in question.
*/
@Test
public void testCreateIfAbsentKeyNotPresent() throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Integer> init = EasyMock
.createMock(ConcurrentInitializer.class);
final String key = "testKey";
final Integer value = 42;
EasyMock.expect(init.get()).andReturn(value);
EasyMock.replay(init);
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
assertEquals("Wrong result", value,
ConcurrentUtils.createIfAbsent(map, key, init));
assertEquals("Wrong value in map", value, map.get(key));
EasyMock.verify(init);
}
/**
* Tests createIfAbsent() if a null map is passed in.
*/
@Test
public void testCreateIfAbsentNullMap() throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Integer> init = EasyMock
.createMock(ConcurrentInitializer.class);
EasyMock.replay(init);
assertNull("Wrong result",
ConcurrentUtils.createIfAbsent(null, "test", init));
EasyMock.verify(init);
}
/**
* Tests createIfAbsent() if a null initializer is passed in.
*/
@Test
public void testCreateIfAbsentNullInit() throws ConcurrentException {
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
final String key = "testKey";
final Integer value = 42;
map.put(key, value);
assertNull("Wrong result",
ConcurrentUtils.createIfAbsent(map, key, null));
assertEquals("Map was changed", value, map.get(key));
}
/**
* Tests createIfAbsentUnchecked() if no exception is thrown.
*/
@Test
public void testCreateIfAbsentUncheckedSuccess() {
final String key = "testKey";
final Integer value = 42;
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
assertEquals("Wrong result", value,
ConcurrentUtils.createIfAbsentUnchecked(map, key,
new ConstantInitializer<Integer>(value)));
assertEquals("Wrong value in map", value, map.get(key));
}
/**
* Tests createIfAbsentUnchecked() if an exception is thrown.
*/
@Test
public void testCreateIfAbsentUncheckedException()
throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Integer> init = EasyMock
.createMock(ConcurrentInitializer.class);
Exception ex = new Exception();
EasyMock.expect(init.get()).andThrow(new ConcurrentException(ex));
EasyMock.replay(init);
try {
ConcurrentUtils.createIfAbsentUnchecked(
new ConcurrentHashMap<String, Integer>(), "test", init);
fail("Exception not thrown!");
} catch (ConcurrentRuntimeException crex) {
assertEquals("Wrong cause", ex, crex.getCause());
}
EasyMock.verify(init);
}
}