[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;
|
package org.apache.commons.lang3.concurrent;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
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>
|
* <p>
|
||||||
|
|
|
@ -23,6 +23,8 @@ import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
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.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -405,4 +407,142 @@ public class ConcurrentUtilsTest {
|
||||||
assertFalse(test.cancel(false));
|
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