[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:
parent
536ea90fc0
commit
3c286d89d0
|
@ -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 class ConcurrentUtils {
|
|||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <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>
|
||||
|
|
|
@ -23,6 +23,8 @@ import static org.junit.Assert.assertSame;
|
|||
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 class ConcurrentUtilsTest {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue