Author: oheger
Date: Wed Feb 16 21:20:27 2011
New Revision: 1071401
URL: http://svn.apache.org/viewvc?rev=1071401&view=rev
Log:
[LANG-678] Added support for ConcurrentMap.putIfAbsent() to ConcurrentUtils.
Modified:
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java
Modified:
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java?rev=1071401&r1=1071400&r2=1071401&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java
(original)
+++
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java
Wed Feb 16 21:20:27 2011
@@ -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;
@@ -207,6 +208,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>
* Gets an implementation of <code>Future</code> that is immediately done
* and returns the specified constant value.
* </p>
Modified:
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java?rev=1071401&r1=1071400&r2=1071401&view=diff
==============================================================================
---
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java
(original)
+++
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java
Wed Feb 16 21:20:27 2011
@@ -23,6 +23,8 @@ import static org.junit.Assert.assertSam
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);
+ }
}