From 3c286d89d08d0cfedb9eb735ca3e65d2c33c5da8 Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Wed, 16 Feb 2011 21:20:27 +0000 Subject: [PATCH] [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 --- .../lang3/concurrent/ConcurrentUtils.java | 100 +++++++++++++ .../lang3/concurrent/ConcurrentUtilsTest.java | 140 ++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java index f5b1aed89..f37c4bbe2 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/ConcurrentUtils.java @@ -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 initializeUnchecked(ConcurrentInitializer initializer) { } } + //----------------------------------------------------------------------- + /** + *

+ * 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: + * + *

+     * if (!map.containsKey(key)) {
+     *     map.put(key, value);
+     *     return value;
+     * } else {
+     *     return map.get(key);
+     * }
+     * 
+ * + * except that the action is performed atomically. So this method always + * returns the value which is stored in the map. + *

+ *

+ * This method is null-safe: It accepts a null map as input + * without throwing an exception. In this case the return value is + * null, too. + *

+ * + * @param the type of the keys of the map + * @param 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 V putIfAbsent(ConcurrentMap 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 null; in this + * case this method simply returns null. + * + * @param the type of the keys of the map + * @param 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 V createIfAbsent(ConcurrentMap map, K key, + ConcurrentInitializer 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 the type of the keys of the map + * @param 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 V createIfAbsentUnchecked(ConcurrentMap map, + K key, ConcurrentInitializer init) { + try { + return createIfAbsent(map, key, init); + } catch (ConcurrentException cex) { + throw new ConcurrentRuntimeException(cex.getCause()); + } + } + //----------------------------------------------------------------------- /** *

diff --git a/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java b/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java index 05c630db5..9c0123b95 100644 --- a/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/concurrent/ConcurrentUtilsTest.java @@ -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 map = new ConcurrentHashMap(); + 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 map = new ConcurrentHashMap(); + 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 init = EasyMock + .createMock(ConcurrentInitializer.class); + EasyMock.replay(init); + final String key = "testKey"; + final Integer value = 42; + ConcurrentMap map = new ConcurrentHashMap(); + 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 init = EasyMock + .createMock(ConcurrentInitializer.class); + final String key = "testKey"; + final Integer value = 42; + EasyMock.expect(init.get()).andReturn(value); + EasyMock.replay(init); + ConcurrentMap map = new ConcurrentHashMap(); + 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 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 map = new ConcurrentHashMap(); + 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 map = new ConcurrentHashMap(); + assertEquals("Wrong result", value, + ConcurrentUtils.createIfAbsentUnchecked(map, key, + new ConstantInitializer(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 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(), "test", init); + fail("Exception not thrown!"); + } catch (ConcurrentRuntimeException crex) { + assertEquals("Wrong cause", ex, crex.getCause()); + } + EasyMock.verify(init); + } }