Replace ReferenceCache w/ MapMaker.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@830 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/copy.sh b/copy.sh
new file mode 100755
index 0000000..80bb0e9
--- /dev/null
+++ b/copy.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+# Copies classes into Guice's internal package.
+
+client=/usr/local/google/clients/collect/google3
+
+srcdir=src/com/google/inject/internal
+testdir=test/com/google/inject/internal
+
+filter() {
+ sed 's/com.google.common.base.internal/com.google.inject.internal/' | \
+ sed 's/com.google.common.base/com.google.inject.internal/' | \
+ sed 's/com.google.common.collect/com.google.inject.internal/'
+}
+
+copy() {
+ inFile=$1;
+ fileName=`basename $inFile`
+ dest=$2
+ destpath=$dest/$fileName
+ filter < $client/${inFile} > $destpath
+}
+
+commonpath=java/com/google/common
+
+copy $commonpath/collect/ComputationException.java $srcdir
+copy $commonpath/collect/AsynchronousComputationException.java $srcdir
+copy $commonpath/collect/CustomConcurrentHashMap.java $srcdir
+copy $commonpath/collect/ExpirationTimer.java $srcdir
+copy $commonpath/collect/MapMaker.java $srcdir
+copy $commonpath/collect/NullOutputException.java $srcdir
+copy $commonpath/base/Function.java $srcdir
+copy $commonpath/base/Nullable.java $srcdir
+copy $commonpath/base/FinalizableReference.java $srcdir
+copy $commonpath/base/FinalizableReferenceQueue.java $srcdir
+copy $commonpath/base/internal/Finalizer.java $srcdir
+copy $commonpath/base/FinalizableWeakReference.java $srcdir
+copy $commonpath/base/FinalizableSoftReference.java $srcdir
+copy $commonpath/base/FinalizablePhantomReference.java $srcdir
+
+commontestspath=javatests/com/google/common
+
+copy $commontestspath/base/FinalizableReferenceQueueTest.java $testdir
+copy $commontestspath/collect/MapMakerTestSuite.java $testdir
+copy $commontestspath/collect/Jsr166HashMap.java $testdir
+copy $commontestspath/collect/Jsr166HashMapTest.java $testdir
+copy $commonpath/collect/ForwardingConcurrentMap.java $testdir
+copy $commonpath/collect/ForwardingMap.java $testdir
+copy $commonpath/collect/ForwardingCollection.java $testdir
+copy $commonpath/collect/ForwardingObject.java $testdir
+copy $commonpath/collect/ForwardingSet.java $testdir
+copy $commonpath/collect/ForwardingMap.java $testdir
+copy $commonpath/base/Preconditions.java $testdir
+
+chmod +w -R src test
diff --git a/extensions/assistedinject/assistedinject.iml b/extensions/assistedinject/assistedinject.iml
index 7d3a9e5..986b7a7 100644
--- a/extensions/assistedinject/assistedinject.iml
+++ b/extensions/assistedinject/assistedinject.iml
@@ -18,6 +18,7 @@
<SOURCES />
</library>
</orderEntry>
+ <orderEntryProperties />
</component>
</module>
diff --git a/extensions/grapher/grapher.iml b/extensions/grapher/grapher.iml
index 783f434..153182a 100644
--- a/extensions/grapher/grapher.iml
+++ b/extensions/grapher/grapher.iml
@@ -11,6 +11,7 @@
<orderEntry type="module" module-name="guice" exported="" />
<orderEntry type="module" module-name="assistedinject" />
<orderEntry type="module" module-name="multibindings" />
+ <orderEntryProperties />
</component>
</module>
diff --git a/extensions/multibindings/multibindings.iml b/extensions/multibindings/multibindings.iml
index ad94b9f..268516a 100644
--- a/extensions/multibindings/multibindings.iml
+++ b/extensions/multibindings/multibindings.iml
@@ -9,6 +9,7 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="guice" exported="" />
+ <orderEntryProperties />
</component>
</module>
diff --git a/extensions/throwingproviders/throwingproviders.iml b/extensions/throwingproviders/throwingproviders.iml
index 4bbe10b..867da96 100644
--- a/extensions/throwingproviders/throwingproviders.iml
+++ b/extensions/throwingproviders/throwingproviders.iml
@@ -18,6 +18,7 @@
<SOURCES />
</library>
</orderEntry>
+ <orderEntryProperties />
</component>
</module>
diff --git a/guice.iml b/guice.iml
index f6cbffc..eb0b2c7 100644
--- a/guice.iml
+++ b/guice.iml
@@ -91,6 +91,7 @@
</SOURCES>
</library>
</orderEntry>
+ <orderEntryProperties />
</component>
</module>
diff --git a/guice.ipr b/guice.ipr
index e24e9ed..5145ba2 100644
--- a/guice.ipr
+++ b/guice.ipr
@@ -7,7 +7,6 @@
<antReference projectDefault="true" />
<customJdkName value="1.5" />
<maximumHeapSize value="128" />
- <maximumStackSize value="32" />
<properties />
</buildFile>
</component>
@@ -205,102 +204,102 @@
<option name="myLocal" value="false" />
<inspection_tool class="SSBasedInspection" level="WARNING" enabled="true">
<replaceConfiguration name="use Lists.newArrayList" text="java.util.List<$T$> $var$ = new java.util.ArrayList<$T$>();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List<$T$> $var$ = com.google.common.collect.Lists.newArrayList();">
- <constraint name="var" within="" contains="" />
- <constraint name="T" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="T" />
</replaceConfiguration>
<replaceConfiguration name="use Lists.newArrayList (iterable)" text="java.util.List<$T$> $var$ = new java.util.ArrayList<$T$>($iterable$);" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List<$T$> $var$ = com.google.common.collect.Lists.newArrayList($iterable$);">
- <constraint name="var" within="" contains="" />
- <constraint name="T" within="" contains="" />
- <constraint name="iterable" nameOfExprType="java.lang.Iterable" exprTypeWithinHierarchy="true" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="T" />
+ <constraint name="iterable" nameOfExprType="java.lang.Iterable" exprTypeWithinHierarchy="true" />
</replaceConfiguration>
<replaceConfiguration name="use Lists.newLinkedList" text="java.util.List<$T$> $var$ = new java.util.LinkedList<$T$>();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List<$T$> $var$ = com.google.common.collect.Lists.newLinkedList();">
- <constraint name="var" within="" contains="" />
- <constraint name="T" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="T" />
</replaceConfiguration>
<replaceConfiguration name="use Lists.newLinkedList (iterable)" text="java.util.List<$T$> $var$ = new java.util.LinkedList<$T$>($iterable$);" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List<$T$> $var$ = com.google.common.collect.Lists.newLinkedList($iterable$);">
- <constraint name="var" within="" contains="" />
- <constraint name="iterable" nameOfExprType="java.lang.Iterable" exprTypeWithinHierarchy="true" within="" contains="" />
- <constraint name="T" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="iterable" nameOfExprType="java.lang.Iterable" exprTypeWithinHierarchy="true" />
+ <constraint name="T" />
</replaceConfiguration>
<replaceConfiguration name="use Sets.newHashSet" text="java.util.Set<$T$> $var$ = new java.util.HashSet<$T$>();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.Set<$T$> $var$ = com.google.common.collect.Sets.newHashSet();">
- <constraint name="var" within="" contains="" />
- <constraint name="T" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="T" />
</replaceConfiguration>
<replaceConfiguration name="use Sets.newHashSet (iterable)" text="java.util.Set<$T$> $var$ = new HashSet<$T$>($iterable$);" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.Set<$T$> $var$ = com.google.common.collect.Sets.newHashSet($iterable$);">
- <constraint name="var" within="" contains="" />
- <constraint name="T" within="" contains="" />
- <constraint name="iterable" nameOfExprType="java.lang.Iterable" exprTypeWithinHierarchy="true" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="T" />
+ <constraint name="iterable" nameOfExprType="java.lang.Iterable" exprTypeWithinHierarchy="true" />
</replaceConfiguration>
<replaceConfiguration name="use Maps.newHashMap" text="java.util.Map<$K$, $V$> $var$ = new java.util.HashMap<$K$, $V$>();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.Map<$K$, $V$> $var$ = com.google.common.collect.Maps.newHashMap();">
- <constraint name="var" within="" contains="" />
- <constraint name="K" within="" contains="" />
- <constraint name="V" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="K" />
+ <constraint name="V" />
</replaceConfiguration>
<replaceConfiguration name="use Multimap<K, V> instead of Map<K, List<V>>" text="java.util.Map<$K$, java.util.List<$V$>> $var$ = $expr$;" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Multimap<$K$, $V$> $var$ = $expr$;">
- <constraint name="K" within="" contains="" />
- <constraint name="V" within="" contains="" />
- <constraint name="var" within="" contains="" />
- <constraint name="expr" within="" contains="" />
+ <constraint name="K" />
+ <constraint name="V" />
+ <constraint name="var" />
+ <constraint name="expr" />
</replaceConfiguration>
<replaceConfiguration name="use Multimap<K, V> instead of Map<K, Set<V>>" text="java.util.Map<$K$, java.util.Set<$V$>> $var$ = $expr$;" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Multimap<$K$, $V$> $var$ = $expr$;">
- <constraint name="K" within="" contains="" />
- <constraint name="V" within="" contains="" />
- <constraint name="var" within="" contains="" />
- <constraint name="expr" within="" contains="" />
+ <constraint name="K" />
+ <constraint name="V" />
+ <constraint name="var" />
+ <constraint name="expr" />
</replaceConfiguration>
<replaceConfiguration name="use Preconditions.checkState" text="if ($condition$) { throw new IllegalStateException($message$); }" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.base.Preconditions.checkState(!($condition$), $message$);">
- <constraint name="condition" within="" contains="" />
- <constraint name="message" nameOfExprType="java.lang.String" within="" contains="" />
+ <constraint name="condition" />
+ <constraint name="message" nameOfExprType="java.lang.String" />
</replaceConfiguration>
<replaceConfiguration name="use Preconditions.checkArgument" text="if ($argumentCondition$) { throw new IllegalArgumentException($message$); }" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.base.Preconditions.checkArgument(!($argumentCondition$), $message$);">
- <constraint name="message" nameOfExprType="java.lang.String" within="" contains="" />
- <constraint name="argumentCondition" within="" contains="" />
+ <constraint name="message" nameOfExprType="java.lang.String" />
+ <constraint name="argumentCondition" />
</replaceConfiguration>
<replaceConfiguration name="use Preconditions.checkNotNull" text="if ($var$ == null) { throw new NullPointerException($message$); }" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.base.Preconditions.checkNotNull($var$, $message$);">
- <constraint name="var" within="" contains="" />
- <constraint name="message" nameOfExprType="java.lang.String" within="" contains="" />
+ <constraint name="var" />
+ <constraint name="message" nameOfExprType="java.lang.String" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.max" text="$a$.compareTo($b$) > 0 ? $a$ : $b$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.max($b$, $a$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.max for >=" text="$a$.compareTo($b$) >= 0 ? $a$ : $b$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.max($a$, $b$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.min" text="$a$.compareTo($b$) < 0 ? $a$ : $b$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.min($b$, $a$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.min for <=" text="$a$.compareTo($b$) <= 0 ? $a$ : $b$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.min($a$, $b$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.min (2)" text="$a$.compareTo($b$) > 0 ? $b$ : $a$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.min($a$, $b$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.min (2, >=)" text="$a$.compareTo($b$) >= 0 ? $b$ : $a$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.min($b$, $a$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.max (2)" text="$a$.compareTo($b$) < 0 ? $b$ : $a$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.max($a$, $b$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Comparators.max (2, <=)" text="$a$.compareTo($b$) <= 0 ? $b$ : $a$" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Comparators.max($b$, $a$)">
- <constraint name="a" within="" contains="" />
- <constraint name="b" within="" contains="" />
+ <constraint name="a" />
+ <constraint name="b" />
</replaceConfiguration>
<replaceConfiguration name="use Preconditions.checkState()" text="if ($condition$) { throw new IllegalStateException(); }" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.base.Preconditions.checkState(!($condition$));">
- <constraint name="condition" within="" contains="" />
+ <constraint name="condition" />
</replaceConfiguration>
<replaceConfiguration name="use Preconditions.checkArgument()" text="if ($condition$) { throw new IllegalArgumentException(); }" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.base.Preconditions.checkArgument(!($condition$));">
- <constraint name="condition" within="" contains="" />
+ <constraint name="condition" />
</replaceConfiguration>
<replaceConfiguration name="use Preconditions.checkNotNull()" text="if ($var$ == null) { throw new NullPointerException(); }" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.base.Preconditions.checkNotNull($var$);">
- <constraint name="reference" within="" contains="" />
- <constraint name="var" within="" contains="" />
+ <constraint name="reference" />
+ <constraint name="var" />
</replaceConfiguration>
</inspection_tool>
<inspection_tool class="UnnecessaryLocalVariable" level="WARNING" enabled="true">
@@ -337,10 +336,6 @@
<option name="myAdditionalJavadocTags" value="" />
</inspection_tool>
<inspection_tool class="ThrowableInstanceNeverThrown" level="WARNING" enabled="false" />
- <inspection_tool class="InjectOfNonPublicMember" level="WARNING" enabled="false" />
- <inspection_tool class="ThrowableResultOfMethodCallIgnored" level="WARNING" enabled="false" />
- <inspection_tool class="BindingAnnotationWithoutInject" level="WARNING" enabled="false" />
- <inspection_tool class="PointlessBinding" level="WARNING" enabled="false" />
</profile>
</profiles>
<list size="4">
@@ -526,7 +521,7 @@
<module fileurl="file://$PROJECT_DIR$/extensions/throwingproviders/throwingproviders.iml" filepath="$PROJECT_DIR$/extensions/throwingproviders/throwingproviders.iml" />
</modules>
</component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" project-jdk-type="JavaSDK">
+ <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
<component name="ResourceManagerContainer">
@@ -562,7 +557,7 @@
</entry>
</map>
</option>
- <option name="myVersion" value="124" />
+ <option name="myVersion" value="123" />
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="svn" />
diff --git a/servlet/servlet.iml b/servlet/servlet.iml
index 32b98a5..3667689 100644
--- a/servlet/servlet.iml
+++ b/servlet/servlet.iml
@@ -36,6 +36,7 @@
<SOURCES />
</library>
</orderEntry>
+ <orderEntryProperties />
</component>
</module>
diff --git a/spring/spring.iml b/spring/spring.iml
index cc586c0..f4e1cec 100644
--- a/spring/spring.iml
+++ b/spring/spring.iml
@@ -45,6 +45,7 @@
<SOURCES />
</library>
</orderEntry>
+ <orderEntryProperties />
</component>
</module>
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index fb0bbd8..6a1560a 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -23,7 +23,8 @@
import com.google.inject.internal.BytecodeGen;
import com.google.inject.internal.BytecodeGen.Visibility;
import static com.google.inject.internal.BytecodeGen.newEnhancer;
-import com.google.inject.internal.ReferenceCache;
+import com.google.inject.internal.Function;
+import com.google.inject.internal.MapMaker;
import com.google.inject.spi.InjectionPoint;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -54,19 +55,20 @@
defaultFactory = new DefaultConstructionProxyFactory();
}
+ /** Cached construction proxies for each injection point */
+ Map<InjectionPoint, ConstructionProxy> constructionProxies = new MapMaker().makeComputingMap(
+ new Function<InjectionPoint, ConstructionProxy>() {
+ public ConstructionProxy apply(InjectionPoint key) {
+ return createConstructionProxy(key);
+ }
+ });
+
@SuppressWarnings("unchecked") // the constructed T is the same as the injection point's T
public <T> ConstructionProxy<T> get(InjectionPoint injectionPoint) {
return (ConstructionProxy<T>) constructionProxies.get(injectionPoint);
}
- /** Cached construction proxies for each injection point */
- ReferenceCache<InjectionPoint, ConstructionProxy> constructionProxies
- = new ReferenceCache<InjectionPoint, ConstructionProxy>() {
- protected ConstructionProxy create(InjectionPoint key) {
- return createConstructionProxy(key);
- }
- };
-
+ // TODO: This isn't safe.
<T> ConstructionProxy<T> createConstructionProxy(InjectionPoint injectionPoint) {
@SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T>
Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
diff --git a/src/com/google/inject/internal/AbstractReferenceCache.java b/src/com/google/inject/internal/AbstractReferenceCache.java
deleted file mode 100644
index 77d5ed4..0000000
--- a/src/com/google/inject/internal/AbstractReferenceCache.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-/**
- * Supports cache implementations.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-abstract class AbstractReferenceCache<K, V> extends ReferenceMap<K, V> {
-
- private static final long serialVersionUID = 0;
-
- transient ConcurrentMap<Object, FutureValue<V>> futures =
- new ConcurrentHashMap<Object, FutureValue<V>>();
-
- AbstractReferenceCache(ReferenceType keyReferenceType,
- ReferenceType valueReferenceType) {
- super(keyReferenceType, valueReferenceType);
- }
-
- V internalCreate(K key) {
- FutureValue<V> futureValue = new FutureValue<V>();
-
- // use a reference so we get the correct equality semantics.
- Object keyReference = referenceKey(key);
- FutureValue<V> previous =
- futures.putIfAbsent(keyReference, futureValue);
- if (previous == null) {
- // winning thread.
- try {
- // check one more time (a previous future could have come and gone.)
- V value = internalGet(key);
- if (value != null) {
- futureValue.setValue(value);
- return value;
- }
-
- try {
- value = create(futureValue, key);
- if (value == null) {
- throw new NullPointerException(
- "create() returned null for: " + key);
- }
- futureValue.setValue(value);
- } catch (Throwable t) {
- futureValue.setThrowable(t);
- rethrow(t);
- }
-
- putStrategy().execute(
- this, keyReference, referenceValue(keyReference, value));
-
- return value;
- } finally {
- futures.remove(keyReference);
- }
- } else {
- if (previous.winningThread() == Thread.currentThread()) {
- throw new RuntimeException("Circular reference: " + key);
- }
-
- // wait for winning thread.
- return previous.get();
- }
- }
-
- private static void rethrow(Throwable t) {
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
- if (t instanceof Error) {
- throw (Error) t;
- }
- throw new RuntimeException(t);
- }
-
- /**
- * Creates a value for the given key.
- */
- abstract V create(FutureValue<V> futureValue, K key);
-
- /**
- * {@inheritDoc}
- *
- * If this map does not contain an entry for the given key, this method will
- * create a new value, put it in the map, and return it. The value
- * is canonical (i.e. only one value will be created for each key).
- *
- * @throws NullPointerException if the value is {@code null}
- * @throws java.util.concurrent.CancellationException if the value creation
- * is cancelled.
- */
- @SuppressWarnings("unchecked")
- @Override public V get(final Object key) {
- V value = super.get(key);
- return (value == null)
- ? internalCreate((K) key)
- : value;
- }
-
- private void readObject(ObjectInputStream in) throws IOException,
- ClassNotFoundException {
- in.defaultReadObject();
- this.futures = new ConcurrentHashMap<Object, FutureValue<V>>();
- }
-
- /**
- * Synchronizes threads waiting for the same value.
- */
- static class FutureValue<V> {
-
- /** True if the result has been set. */
- private boolean set = false;
-
- /** The result is a value. */
- private V value;
-
- /** The result is a throwable. */
- private Throwable t;
-
- private final Thread winningThread = Thread.currentThread();
-
- Thread winningThread() {
- return winningThread;
- }
-
- synchronized V get() {
- if (!set) {
- boolean interrupted = waitUntilSet();
-
- if (interrupted) {
- Thread.currentThread().interrupt();
- }
- }
-
- if (t != null) {
- rethrow(t);
- }
- return value;
- }
-
- /**
- * Waits until a value is set.
- *
- * @return {@code true} if the thread was interrupted while waiting
- */
- private boolean waitUntilSet() {
- boolean interrupted = false;
- while (!set) {
- try {
- wait();
- } catch (InterruptedException e) {
- interrupted = true;
- }
- }
- return interrupted;
- }
-
- synchronized void setValue(V v) {
- set();
- value = v;
- }
-
- synchronized void setThrowable(Throwable t) {
- set();
- this.t = t;
- }
-
- private void set() {
- if (set) {
- throw new IllegalStateException("Value is already set.");
- }
- set = true;
- notifyAll();
- }
- }
-}
diff --git a/src/com/google/inject/internal/AsynchronousComputationException.java b/src/com/google/inject/internal/AsynchronousComputationException.java
new file mode 100644
index 0000000..e22ec2d
--- /dev/null
+++ b/src/com/google/inject/internal/AsynchronousComputationException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+/**
+ * Wraps an exception that occured during a computation in a different thread.
+ *
+ * @author Bob Lee
+ */
+public class AsynchronousComputationException extends ComputationException {
+
+ public AsynchronousComputationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/com/google/inject/internal/BytecodeGen.java b/src/com/google/inject/internal/BytecodeGen.java
index 6992fba..64b2ced 100644
--- a/src/com/google/inject/internal/BytecodeGen.java
+++ b/src/com/google/inject/internal/BytecodeGen.java
@@ -16,13 +16,13 @@
package com.google.inject.internal;
-import static com.google.inject.internal.ReferenceType.WEAK;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.util.Map;
import java.util.logging.Logger;
import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.core.NamingPolicy;
@@ -86,17 +86,18 @@
* Weak cache of bridge class loaders that make the Guice implementation
* classes visible to various code-generated proxies of client classes.
*/
- private static final ReferenceCache<ClassLoader, ClassLoader> CLASS_LOADER_CACHE
- = new ReferenceCache<ClassLoader, ClassLoader>(WEAK, WEAK) {
- @Override protected ClassLoader create(final ClassLoader typeClassLoader) {
- logger.fine("Creating a bridge ClassLoader for " + typeClassLoader);
- return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
- public ClassLoader run() {
- return new BridgeClassLoader(typeClassLoader);
- }
- });
+ private static final Map<ClassLoader, ClassLoader> CLASS_LOADER_CACHE
+ = new MapMaker().weakKeys().weakValues().makeComputingMap(
+ new Function<ClassLoader, ClassLoader>() {
+ public ClassLoader apply(final @Nullable ClassLoader typeClassLoader) {
+ logger.fine("Creating a bridge ClassLoader for " + typeClassLoader);
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return new BridgeClassLoader(typeClassLoader);
}
- };
+ });
+ }
+ });
/**
* For class loaders, {@code null}, is always an alias to the
diff --git a/src/com/google/inject/internal/ComputationException.java b/src/com/google/inject/internal/ComputationException.java
new file mode 100644
index 0000000..a41700b
--- /dev/null
+++ b/src/com/google/inject/internal/ComputationException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+/**
+ * Wraps an exception that occured during a computation.
+ */
+public class ComputationException extends RuntimeException {
+
+ public ComputationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/com/google/inject/internal/CustomConcurrentHashMap.java b/src/com/google/inject/internal/CustomConcurrentHashMap.java
new file mode 100644
index 0000000..2128749
--- /dev/null
+++ b/src/com/google/inject/internal/CustomConcurrentHashMap.java
@@ -0,0 +1,2113 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.AbstractCollection;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A framework for concurrent hash map implementations. The
+ * CustomConcurrentHashMap class itself is not extensible and does not contain
+ * any methods. Use {@link Builder} to create a custom concurrent hash map
+ * instance. Client libraries implement {@link Strategy}, and this class
+ * provides the surrounding concurrent data structure which implements {@link
+ * ConcurrentMap}. Additionally supports implementing maps where {@link
+ * Map#get} atomically computes values on demand (see {@link
+ * Builder#buildComputingMap(CustomConcurrentHashMap.ComputingStrategy, Function)}).
+ *
+ * <p>The resulting hash table supports full concurrency of retrievals and
+ * adjustable expected concurrency for updates. Even though all operations are
+ * thread-safe, retrieval operations do <i>not</i> entail locking,
+ * and there is <i>not</i> any support for locking the entire table
+ * in a way that prevents all access.
+ *
+ * <p>Retrieval operations (including {@link Map#get}) generally do not
+ * block, so may overlap with update operations (including
+ * {@link Map#put} and {@link Map#remove}). Retrievals reflect the results
+ * of the most recently <i>completed</i> update operations holding
+ * upon their onset. For aggregate operations such as {@link Map#putAll}
+ * and {@link Map#clear}, concurrent retrievals may reflect insertion or
+ * removal of only some entries. Similarly, iterators return elements
+ * reflecting the state of the hash table at some point at or since the
+ * creation of the iterator. They do <i>not</i> throw
+ * {@link java.util.ConcurrentModificationException}. However, iterators can
+ * only be used by one thread at a time.
+ *
+ * <p>The resulting {@link ConcurrentMap} and its views and iterators implement
+ * all of the <i>optional</i> methods of the {@link java.util.Map} and {@link
+ * java.util.Iterator} interfaces. Partially reclaimed entries are never
+ * exposed through the views or iterators.
+ *
+ * <p>For example, the following strategy emulates the behavior of
+ * {@link java.util.concurrent.ConcurrentHashMap}:
+ *
+ * <pre> {@code
+ * class ConcurrentHashMapStrategy<K, V>
+ * implements CustomConcurrentHashMap.Strategy<K, V,
+ * InternalEntry<K, V>>, Serializable {
+ * public InternalEntry<K, V> newEntry(K key, int hash,
+ * InternalEntry<K, V> next) {
+ * return new InternalEntry<K, V>(key, hash, null, next);
+ * }
+ * public InternalEntry<K, V> copyEntry(K key,
+ * InternalEntry<K, V> original, InternalEntry<K, V> next) {
+ * return new InternalEntry<K, V>(key, original.hash, original.value, next);
+ * }
+ * public void setValue(InternalEntry<K, V> entry, V value) {
+ * entry.value = value;
+ * }
+ * public V getValue(InternalEntry<K, V> entry) { return entry.value; }
+ * public boolean equalKeys(K a, Object b) { return a.equals(b); }
+ * public boolean equalValues(V a, Object b) { return a.equals(b); }
+ * public int hashKey(Object key) { return key.hashCode(); }
+ * public K getKey(InternalEntry<K, V> entry) { return entry.key; }
+ * public InternalEntry<K, V> getNext(InternalEntry<K, V> entry) {
+ * return entry.next;
+ * }
+ * public int getHash(InternalEntry<K, V> entry) { return entry.hash; }
+ * public void setInternals(CustomConcurrentHashMap.Internals<K, V,
+ * InternalEntry<K, V>> internals) {} // ignored
+ * }
+ *
+ * class InternalEntry<K, V> {
+ * final K key;
+ * final int hash;
+ * volatile V value;
+ * final InternalEntry<K, V> next;
+ * InternalEntry(K key, int hash, V value, InternalEntry<K, V> next) {
+ * this.key = key;
+ * this.hash = hash;
+ * this.value = value;
+ * this.next = next;
+ * }
+ * }
+ * }</pre>
+ *
+ * To create a {@link java.util.concurrent.ConcurrentMap} using the strategy
+ * above:
+ *
+ * <pre>{@code
+ * ConcurrentMap<K, V> map = new CustomConcurrentHashMap.Builder()
+ * .build(new ConcurrentHashMapStrategy<K, V>());
+ * }</pre>
+ *
+ * @author Bob Lee
+ * @author Doug Lea
+ */
+public final class CustomConcurrentHashMap {
+
+ /** Prevents instantiation. */
+ private CustomConcurrentHashMap() {}
+
+ /**
+ * Builds a custom concurrent hash map.
+ */
+ public final static class Builder {
+
+ float loadFactor = 0.75f;
+ int initialCapacity = 16;
+ int concurrencyLevel = 16;
+
+ /**
+ * Sets a custom load factor (defaults to 0.75).
+ *
+ * @throws IllegalArgumentException if loadFactor <= 0
+ */
+ public Builder loadFactor(float loadFactor) {
+ if (loadFactor <= 0) {
+ throw new IllegalArgumentException();
+ }
+ this.loadFactor = loadFactor;
+ return this;
+ }
+
+ /**
+ * Sets a custom initial capacity (defaults to 16). Resizing this or any
+ * other kind of hash table is a relatively slow operation, so, when
+ * possible, it is a good idea to provide estimates of expected table
+ * sizes.
+ *
+ * @throws IllegalArgumentException if initialCapacity < 0
+ */
+ public Builder initialCapacity(int initialCapacity) {
+ if (initialCapacity < 0) {
+ throw new IllegalArgumentException();
+ }
+ this.initialCapacity = initialCapacity;
+ return this;
+ }
+
+ /**
+ * Guides the allowed concurrency among update operations. Used as a
+ * hint for internal sizing. The table is internally partitioned to try to
+ * permit the indicated number of concurrent updates without contention.
+ * Because placement in hash tables is essentially random, the actual
+ * concurrency will vary. Ideally, you should choose a value to accommodate
+ * as many threads as will ever concurrently modify the table. Using a
+ * significantly higher value than you need can waste space and time,
+ * and a significantly lower value can lead to thread contention. But
+ * overestimates and underestimates within an order of magnitude do
+ * not usually have much noticeable impact. A value of one is
+ * appropriate when it is known that only one thread will modify and
+ * all others will only read. Defaults to {@literal 16}.
+ *
+ * @throws IllegalArgumentException if concurrencyLevel < 0
+ */
+ public Builder concurrencyLevel(int concurrencyLevel) {
+ if (concurrencyLevel <= 0) {
+ throw new IllegalArgumentException();
+ }
+ this.concurrencyLevel = concurrencyLevel;
+ return this;
+ }
+
+ /**
+ * Creates a new concurrent hash map backed by the given strategy.
+ *
+ * @param strategy used to implement and manipulate the entries
+ *
+ * @param <K> the type of keys to be stored in the returned map
+ * @param <V> the type of values to be stored in the returned map
+ * @param <E> the type of internal entry to be stored in the returned map
+ *
+ * @throws NullPointerException if strategy is null
+ */
+ public <K, V, E> ConcurrentMap<K, V> buildMap(Strategy<K, V, E> strategy) {
+ if (strategy == null) {
+ throw new NullPointerException("strategy");
+ }
+ return new Impl<K, V, E>(strategy, this);
+ }
+
+ /**
+ * Creates a {@link ConcurrentMap}, backed by the given strategy, that
+ * supports atomic, on-demand computation of values. {@link Map#get}
+ * returns the value corresponding to the given key, atomically computes
+ * it using the computer function passed to this builder, or waits for
+ * another thread to compute the value if necessary. Only one value will
+ * be computed for each key at a given time.
+ *
+ * <p>If an entry's value has not finished computing yet, query methods
+ * besides {@link java.util.Map#get} return immediately as if an entry
+ * doesn't exist. In other words, an entry isn't externally visible until
+ * the value's computation completes.
+ *
+ * <p>{@link Map#get} in the returned map implementation throws:
+ * <ul>
+ * <li>{@link NullPointerException} if the key is null or the
+ * computer returns null</li>
+ * <li>or {@link ComputationException} wrapping an exception thrown by the
+ * computation</li>
+ * </ul>
+ *
+ * <p><b>Note:</b> Callers of {@code get()} <i>must</i> ensure that the key
+ * argument is of type {@code K}. {@code Map.get()} takes {@code Object},
+ * so the key type is not checked at compile time. Passing an object of
+ * a type other than {@code K} can result in that object being unsafely
+ * passed to the computer function as type {@code K} not to mention the
+ * unsafe key being stored in the map.
+ *
+ * @param strategy used to implement and manipulate the entries
+ * @param computer used to compute values for keys
+ *
+ * @param <K> the type of keys to be stored in the returned map
+ * @param <V> the type of values to be stored in the returned map
+ * @param <E> the type of internal entry to be stored in the returned map
+ *
+ * @throws NullPointerException if strategy or computer is null
+ */
+ public <K, V, E> ConcurrentMap<K, V> buildComputingMap(
+ ComputingStrategy<K, V, E> strategy,
+ Function<? super K, ? extends V> computer) {
+ if (strategy == null) {
+ throw new NullPointerException("strategy");
+ }
+ if (computer == null) {
+ throw new NullPointerException("computer");
+ }
+
+ return new ComputingImpl<K, V, E>(strategy, this, computer);
+ }
+ }
+
+ /**
+ * Implements behavior specific to the client's concurrent hash map
+ * implementation. Used by the framework to create new entries and perform
+ * operations on them.
+ *
+ * <p>Method parameters are never null unless otherwise specified.
+ *
+ * <h3>Partially Reclaimed Entries</h3>
+ *
+ * <p>This class does <i>not</i> allow {@code null} to be used as a key.
+ * Setting values to null is not permitted, but entries may have null keys
+ * or values for various reasons. For example, the key or value may have
+ * been garbage collected or reclaimed through other means.
+ * CustomConcurrentHashMap treats entries with null keys and values as
+ * "partially reclaimed" and ignores them for the most part. Entries may
+ * enter a partially reclaimed state but they must not leave it. Partially
+ * reclaimed entries will not be copied over during table expansions, for
+ * example. Strategy implementations should proactively remove partially
+ * reclaimed entries so that {@link Map#isEmpty} and {@link Map#size()}
+ * return up-to-date results.
+ *
+ * @param <K> the type of keys to be stored in the returned map
+ * @param <V> the type of values to be stored in the returned map
+ * @param <E> internal entry type, not directly exposed to clients in map
+ * views
+ */
+ public interface Strategy<K, V, E> {
+
+ /**
+ * Constructs a new entry for the given key with a pointer to the given
+ * next entry.
+ *
+ * <p>This method may return different entry implementations
+ * depending upon whether next is null or not. For example, if next is
+ * null (as will often be the case), this factory might use an entry
+ * class that doesn't waste memory on an unnecessary field.
+ *
+ * @param key for this entry
+ * @param hash of key returned by {@link #hashKey}
+ * @param next entry (used when implementing a hash bucket as a linked
+ * list, for example), possibly null
+ * @return a new entry
+ */
+ abstract E newEntry(K key, int hash, E next);
+
+ /**
+ * Creates a copy of the given entry pointing to the given next entry.
+ * Copies the value and any other implementation-specific state from
+ * {@code original} to the returned entry. For example,
+ * CustomConcurrentHashMap might use this method when removing other
+ * entries or expanding the internal table.
+ *
+ * @param key for {@code original} as well as the returned entry.
+ * Explicitly provided here to prevent reclamation of the key at an
+ * inopportune time in implementations that don't otherwise keep
+ * a strong reference to the key.
+ * @param original entry from which the value and other
+ * implementation-specific state should be copied
+ * @param newNext the next entry the new entry should point to, possibly
+ * null
+ */
+ E copyEntry(K key, E original, E newNext);
+
+ /**
+ * Sets the value of an entry using volatile semantics. Values are set
+ * synchronously on a per-entry basis.
+ *
+ * @param entry to set the value on
+ * @param value to set
+ */
+ void setValue(E entry, V value);
+
+ /**
+ * Gets the value of an entry using volatile semantics.
+ *
+ * @param entry to get the value from
+ */
+ V getValue(E entry);
+
+ /**
+ * Returns true if the two given keys are equal, false otherwise. Neither
+ * key will be null.
+ *
+ * @param a key from inside the map
+ * @param b key passed from caller, not necesarily of type K
+ *
+ * @see Object#equals the same contractual obligations apply here
+ */
+ boolean equalKeys(K a, Object b);
+
+ /**
+ * Returns true if the two given values are equal, false otherwise. Neither
+ * value will be null.
+ *
+ * @param a value from inside the map
+ * @param b value passed from caller, not necesarily of type V
+ *
+ * @see Object#equals the same contractual obligations apply here
+ */
+ boolean equalValues(V a, Object b);
+
+ /**
+ * Returns a hash code for the given key.
+ *
+ * @see Object#hashCode the same contractual obligations apply here
+ */
+ int hashKey(Object key);
+
+ /**
+ * Gets the key for the given entry. This may return null (for example,
+ * if the key was reclaimed by the garbage collector).
+ *
+ * @param entry to get key from
+ * @return key from the given entry
+ */
+ K getKey(E entry);
+
+ /**
+ * Gets the next entry relative to the given entry, the exact same entry
+ * that was provided to {@link Strategy#newEntry} when the given entry was
+ * created.
+ *
+ * @return the next entry or null if null was passed to
+ * {@link Strategy#newEntry}
+ */
+ E getNext(E entry);
+
+ /**
+ * Returns the hash code that was passed to {@link Strategy#newEntry})
+ * when the given entry was created.
+ */
+ int getHash(E entry);
+
+// TODO:
+// /**
+// * Notifies the strategy that an entry has been removed from the map.
+// *
+// * @param entry that was recently removed
+// */
+// void remove(E entry);
+
+ /**
+ * Provides an API for interacting directly with the map's internal
+ * entries to this strategy. Invoked once when the map is created.
+ * Strategies that don't need access to the map's internal entries
+ * can simply ignore the argument.
+ *
+ * @param internals of the map, enables direct interaction with the
+ * internal entries
+ */
+ void setInternals(Internals<K, V, E> internals);
+ }
+
+ /**
+ * Provides access to a map's internal entries.
+ */
+ public interface Internals<K, V, E> {
+
+// TODO:
+// /**
+// * Returns a set view of the internal entries.
+// */
+// Set<E> entrySet();
+
+ /**
+ * Returns the internal entry corresponding to the given key from the map.
+ *
+ * @param key to retrieve entry for
+ *
+ * @throws NullPointerException if key is null
+ */
+ E getEntry(K key);
+
+ /**
+ * Removes the given entry from the map if the value of the entry in the
+ * map matches the given value.
+ *
+ * @param entry to remove
+ * @param value entry must have for the removal to succeed
+ *
+ * @throws NullPointerException if entry is null
+ */
+ boolean removeEntry(E entry, V value);
+
+ /**
+ * Removes the given entry from the map.
+ *
+ * @param entry to remove
+ *
+ * @throws NullPointerException if entry is null
+ */
+ boolean removeEntry(E entry);
+ }
+
+ /**
+ * Extends {@link Strategy} to add support for computing values on-demand.
+ * Implementations should typically intialize the entry's value to a
+ * placeholder value in {@link #newEntry(Object, int, Object)} so that
+ * {@link #waitForValue(Object)} can tell the difference between a
+ * pre-intialized value and an in-progress computation. {@link
+ * #copyEntry(Object, Object, Object)} must detect and handle the case where
+ * an entry is copied in the middle of a computation. Implementations can
+ * throw {@link UnsupportedOperationException} in {@link #setValue(Object,
+ * Object)} if they wish to prevent users from setting values directly.
+ *
+ * @see Builder#buildComputingMap(ComputingStrategy, Function)
+ */
+ public interface ComputingStrategy<K, V, E> extends Strategy<K, V, E> {
+
+ /**
+ * Computes a value for the given key and stores it in the given entry.
+ * Called as a result of {@link Map#get}. If this method throws an
+ * exception, CustomConcurrentHashMap will remove the entry and retry
+ * the computation on subsequent requests.
+ *
+ * @param entry that was created
+ * @param computer passed to {@link Builder#buildMap}
+ *
+ * @throws ComputationException if the computation threw an exception
+ * @throws NullPointerException if the computer returned null
+ *
+ * @return the computed value
+ */
+ V compute(K key, E entry, Function<? super K, ? extends V> computer);
+
+ /**
+ * Gets a value from an entry waiting for the value to be set by {@link
+ * #compute} if necessary. Returns null if a value isn't available at
+ * which point CustomConcurrentHashMap tries to compute a new value.
+ *
+ * @param entry to return value from
+ * @return stored value or null if the value isn't available
+ *
+ * @throws InterruptedException if the thread was interrupted while
+ * waiting
+ */
+ V waitForValue(E entry) throws InterruptedException;
+ }
+
+ /**
+ * Applies a supplemental hash function to a given hash code, which defends
+ * against poor quality hash functions. This is critical when the
+ * concurrent hash map uses power-of-two length hash tables, that otherwise
+ * encounter collisions for hash codes that do not differ in lower or upper
+ * bits.
+ *
+ * @param h hash code
+ */
+ private static int rehash(int h) {
+ // Spread bits to regularize both segment and index locations,
+ // using variant of single-word Wang/Jenkins hash.
+ h += (h << 15) ^ 0xffffcd7d;
+ h ^= (h >>> 10);
+ h += (h << 3);
+ h ^= (h >>> 6);
+ h += (h << 2) + (h << 14);
+ return h ^ (h >>> 16);
+ }
+
+ /** The concurrent hash map implementation. */
+ static class Impl<K, V, E> extends AbstractMap<K, V>
+ implements ConcurrentMap<K, V>, Serializable {
+
+ /*
+ * The basic strategy is to subdivide the table among Segments,
+ * each of which itself is a concurrently readable hash table.
+ */
+
+ /* ---------------- Constants -------------- */
+
+ /**
+ * The maximum capacity, used if a higher value is implicitly specified by
+ * either of the constructors with arguments. MUST be a power of two <=
+ * 1<<30 to ensure that entries are indexable using ints.
+ */
+ static final int MAXIMUM_CAPACITY = 1 << 30;
+
+ /**
+ * The maximum number of segments to allow; used to bound constructor
+ * arguments.
+ */
+ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
+
+ /**
+ * Number of unsynchronized retries in size and containsValue methods before
+ * resorting to locking. This is used to avoid unbounded retries if tables
+ * undergo continuous modification which would make it impossible to obtain
+ * an accurate result.
+ */
+ static final int RETRIES_BEFORE_LOCK = 2;
+
+ /* ---------------- Fields -------------- */
+
+ /**
+ * The strategy used to implement this map.
+ */
+ final Strategy<K, V, E> strategy;
+
+ /**
+ * Mask value for indexing into segments. The upper bits of a key's hash
+ * code are used to choose the segment.
+ */
+ final int segmentMask;
+
+ /**
+ * Shift value for indexing within segments. Helps prevent entries that
+ * end up in the same segment from also ending up in the same bucket.
+ */
+ final int segmentShift;
+
+ /**
+ * The segments, each of which is a specialized hash table
+ */
+ final Segment[] segments;
+
+ /**
+ * The load factor for the hash table.
+ */
+ final float loadFactor;
+
+ /**
+ * Creates a new, empty map with the specified strategy, initial capacity,
+ * load factor and concurrency level.
+ */
+ Impl(Strategy<K, V, E> strategy, Builder builder) {
+ this.loadFactor = builder.loadFactor;
+ int concurrencyLevel = builder.concurrencyLevel;
+ int initialCapacity = builder.initialCapacity;
+
+ if (concurrencyLevel > MAX_SEGMENTS) {
+ concurrencyLevel = MAX_SEGMENTS;
+ }
+
+ // Find power-of-two sizes best matching arguments
+ int segmentShift = 0;
+ int segmentCount = 1;
+ while (segmentCount < concurrencyLevel) {
+ ++segmentShift;
+ segmentCount <<= 1;
+ }
+ this.segmentShift = 32 - segmentShift;
+ segmentMask = segmentCount - 1;
+ this.segments = newSegmentArray(segmentCount);
+
+ if (initialCapacity > MAXIMUM_CAPACITY) {
+ initialCapacity = MAXIMUM_CAPACITY;
+ }
+
+ int segmentCapacity = initialCapacity / segmentCount;
+ if (segmentCapacity * segmentCount < initialCapacity) {
+ ++segmentCapacity;
+ }
+
+ int segmentSize = 1;
+ while (segmentSize < segmentCapacity) {
+ segmentSize <<= 1;
+ }
+ for (int i = 0; i < this.segments.length; ++i) {
+ this.segments[i] = new Segment(segmentSize);
+ }
+
+ this.strategy = strategy;
+
+ strategy.setInternals(new InternalsImpl());
+ }
+
+ int hash(Object key) {
+ int h = strategy.hashKey(key);
+ return rehash(h);
+ }
+
+ class InternalsImpl implements Internals<K, V, E>, Serializable {
+
+ static final long serialVersionUID = 0;
+
+ public E getEntry(K key) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).getEntry(key, hash);
+ }
+
+ public boolean removeEntry(E entry, V value) {
+ if (entry == null) {
+ throw new NullPointerException("entry");
+ }
+ int hash = strategy.getHash(entry);
+ return segmentFor(hash).removeEntry(entry, hash, value);
+ }
+
+ public boolean removeEntry(E entry) {
+ if (entry == null) {
+ throw new NullPointerException("entry");
+ }
+ int hash = strategy.getHash(entry);
+ return segmentFor(hash).removeEntry(entry, hash);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ Segment[] newSegmentArray(int ssize) {
+ // Note: This is the only way I could figure out how to create
+ // a segment array (the compile has a tough time with arrays of
+ // inner classes of generic types apparently). Safe because we're
+ // restricting what can go in the array and no one has an
+ // unrestricted reference.
+ return (Segment[]) Array.newInstance(Segment.class, ssize);
+ }
+
+ /* ---------------- Small Utilities -------------- */
+
+ /**
+ * Returns the segment that should be used for key with given hash
+ *
+ * @param hash the hash code for the key
+ * @return the segment
+ */
+ Segment segmentFor(int hash) {
+ return segments[(hash >>> segmentShift) & segmentMask];
+ }
+
+ /* ---------------- Inner Classes -------------- */
+
+ /**
+ * Segments are specialized versions of hash tables. This subclasses from
+ * ReentrantLock opportunistically, just to simplify some locking and avoid
+ * separate construction.
+ */
+ final class Segment extends ReentrantLock {
+
+ /*
+ * Segments maintain a table of entry lists that are ALWAYS
+ * kept in a consistent state, so can be read without locking.
+ * Next fields of nodes are immutable (final). All list
+ * additions are performed at the front of each bin. This
+ * makes it easy to check changes, and also fast to traverse.
+ * When nodes would otherwise be changed, new nodes are
+ * created to replace them. This works well for hash tables
+ * since the bin lists tend to be short. (The average length
+ * is less than two for the default load factor threshold.)
+ *
+ * Read operations can thus proceed without locking, but rely
+ * on selected uses of volatiles to ensure that completed
+ * write operations performed by other threads are
+ * noticed. For most purposes, the "count" field, tracking the
+ * number of elements, serves as that volatile variable
+ * ensuring visibility. This is convenient because this field
+ * needs to be read in many read operations anyway:
+ *
+ * - All (unsynchronized) read operations must first read the
+ * "count" field, and should not look at table entries if
+ * it is 0.
+ *
+ * - All (synchronized) write operations should write to
+ * the "count" field after structurally changing any bin.
+ * The operations must not take any action that could even
+ * momentarily cause a concurrent read operation to see
+ * inconsistent data. This is made easier by the nature of
+ * the read operations in Map. For example, no operation
+ * can reveal that the table has grown but the threshold
+ * has not yet been updated, so there are no atomicity
+ * requirements for this with respect to reads.
+ *
+ * As a guide, all critical volatile reads and writes to the
+ * count field are marked in code comments.
+ */
+
+ /**
+ * The number of elements in this segment's region.
+ */
+ volatile int count;
+
+ /**
+ * Number of updates that alter the size of the table. This is used
+ * during bulk-read methods to make sure they see a consistent snapshot:
+ * If modCounts change during a traversal of segments computing size or
+ * checking containsValue, then we might have an inconsistent view of
+ * state so (usually) must retry.
+ */
+ int modCount;
+
+ /**
+ * The table is expanded when its size exceeds this threshold. (The
+ * value of this field is always {@code (int)(capacity * loadFactor)}.)
+ */
+ int threshold;
+
+ /**
+ * The per-segment table.
+ */
+ volatile AtomicReferenceArray<E> table;
+
+ Segment(int initialCapacity) {
+ setTable(newEntryArray(initialCapacity));
+ }
+
+ AtomicReferenceArray<E> newEntryArray(int size) {
+ return new AtomicReferenceArray<E>(size);
+ }
+
+ /**
+ * Sets table to new HashEntry array. Call only while holding lock or in
+ * constructor.
+ */
+ void setTable(AtomicReferenceArray<E> newTable) {
+ this.threshold = (int) (newTable.length() * loadFactor);
+ this.table = newTable;
+ }
+
+ /**
+ * Returns properly casted first entry of bin for given hash.
+ */
+ E getFirst(int hash) {
+ AtomicReferenceArray<E> table = this.table;
+ return table.get(hash & (table.length() - 1));
+ }
+
+ /* Specialized implementations of map methods */
+
+ public E getEntry(Object key, int hash) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ if (count != 0) { // read-volatile
+ for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
+ if (s.getHash(e) != hash) {
+ continue;
+ }
+
+ K entryKey = s.getKey(e);
+ if (entryKey == null) {
+ continue;
+ }
+
+ if (s.equalKeys(entryKey, key)) {
+ return e;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ V get(Object key, int hash) {
+ E entry = getEntry(key, hash);
+ if (entry == null) {
+ return null;
+ }
+
+ return strategy.getValue(entry);
+ }
+
+ boolean containsKey(Object key, int hash) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ if (count != 0) { // read-volatile
+ for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
+ if (s.getHash(e) != hash) {
+ continue;
+ }
+
+ K entryKey = s.getKey(e);
+ if (entryKey == null) {
+ continue;
+ }
+
+ if (s.equalKeys(entryKey, key)) {
+ // Return true only if this entry has a value.
+ return s.getValue(e) != null;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean containsValue(Object value) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ if (count != 0) { // read-volatile
+ AtomicReferenceArray<E> table = this.table;
+ int length = table.length();
+ for (int i = 0; i < length; i++) {
+ for (E e = table.get(i); e != null; e = s.getNext(e)) {
+ V entryValue = s.getValue(e);
+
+ // If the value disappeared, this entry is partially collected,
+ // and we should skip it.
+ if (entryValue == null) {
+ continue;
+ }
+
+ if (s.equalValues(entryValue, value)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean replace(K key, int hash, V oldValue, V newValue) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
+ K entryKey = s.getKey(e);
+ if (s.getHash(e) == hash && entryKey != null
+ && s.equalKeys(key, entryKey)) {
+ // If the value disappeared, this entry is partially collected,
+ // and we should pretend like it doesn't exist.
+ V entryValue = s.getValue(e);
+ if (entryValue == null) {
+ return false;
+ }
+
+ if (s.equalValues(entryValue, oldValue)) {
+ s.setValue(e, newValue);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ }
+ }
+
+ V replace(K key, int hash, V newValue) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
+ K entryKey = s.getKey(e);
+ if (s.getHash(e) == hash && entryKey != null
+ && s.equalKeys(key, entryKey)) {
+ // If the value disappeared, this entry is partially collected,
+ // and we should pretend like it doesn't exist.
+ V entryValue = s.getValue(e);
+ if (entryValue == null) {
+ return null;
+ }
+
+ s.setValue(e, newValue);
+ return entryValue;
+ }
+ }
+
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ V put(K key, int hash, V value, boolean onlyIfAbsent) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ int count = this.count;
+ if (count++ > this.threshold) { // ensure capacity
+ expand();
+ }
+
+ AtomicReferenceArray<E> table = this.table;
+ int index = hash & (table.length() - 1);
+
+ E first = table.get(index);
+
+ // Look for an existing entry.
+ for (E e = first; e != null; e = s.getNext(e)) {
+ K entryKey = s.getKey(e);
+ if (s.getHash(e) == hash && entryKey != null
+ && s.equalKeys(key, entryKey)) {
+ // We found an existing entry.
+
+ // If the value disappeared, this entry is partially collected,
+ // and we should pretend like it doesn't exist.
+ V entryValue = s.getValue(e);
+ if (onlyIfAbsent && entryValue != null) {
+ return entryValue;
+ }
+
+ s.setValue(e, value);
+ return entryValue;
+ }
+ }
+
+ // Create a new entry.
+ ++modCount;
+ E newEntry = s.newEntry(key, hash, first);
+ s.setValue(newEntry, value);
+ table.set(index, newEntry);
+ this.count = count; // write-volatile
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ /**
+ * Expands the table if possible.
+ */
+ void expand() {
+ AtomicReferenceArray<E> oldTable = table;
+ int oldCapacity = oldTable.length();
+ if (oldCapacity >= MAXIMUM_CAPACITY) {
+ return;
+ }
+
+ /*
+ * Reclassify nodes in each list to new Map. Because we are
+ * using power-of-two expansion, the elements from each bin
+ * must either stay at same index, or move with a power of two
+ * offset. We eliminate unnecessary node creation by catching
+ * cases where old nodes can be reused because their next
+ * fields won't change. Statistically, at the default
+ * threshold, only about one-sixth of them need cloning when
+ * a table doubles. The nodes they replace will be garbage
+ * collectable as soon as they are no longer referenced by any
+ * reader thread that may be in the midst of traversing table
+ * right now.
+ */
+
+ Strategy<K, V, E> s = Impl.this.strategy;
+ AtomicReferenceArray<E> newTable = newEntryArray(oldCapacity << 1);
+ threshold = (int) (newTable.length() * loadFactor);
+ int newMask = newTable.length() - 1;
+ for (int oldIndex = 0; oldIndex < oldCapacity; oldIndex++) {
+ // We need to guarantee that any existing reads of old Map can
+ // proceed. So we cannot yet null out each bin.
+ E head = oldTable.get(oldIndex);
+
+ if (head != null) {
+ E next = s.getNext(head);
+ int headIndex = s.getHash(head) & newMask;
+
+ // Single node on list
+ if (next == null) {
+ newTable.set(headIndex, head);
+ } else {
+ // Reuse the consecutive sequence of nodes with the same target
+ // index from the end of the list. tail points to the first
+ // entry in the reusable list.
+ E tail = head;
+ int tailIndex = headIndex;
+ for (E last = next; last != null; last = s.getNext(last)) {
+ int newIndex = s.getHash(last) & newMask;
+ if (newIndex != tailIndex) {
+ // The index changed. We'll need to copy the previous entry.
+ tailIndex = newIndex;
+ tail = last;
+ }
+ }
+ newTable.set(tailIndex, tail);
+
+ // Clone nodes leading up to the tail.
+ for (E e = head; e != tail; e = s.getNext(e)) {
+ K key = s.getKey(e);
+ if (key != null) {
+ int newIndex = s.getHash(e) & newMask;
+ E newNext = newTable.get(newIndex);
+ newTable.set(newIndex, s.copyEntry(key, e, newNext));
+ } else {
+ // Key was reclaimed. Skip entry.
+ }
+ }
+ }
+ }
+ }
+ table = newTable;
+ }
+
+ V remove(Object key, int hash) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ int count = this.count - 1;
+ AtomicReferenceArray<E> table = this.table;
+ int index = hash & (table.length() - 1);
+ E first = table.get(index);
+
+ for (E e = first; e != null; e = s.getNext(e)) {
+ K entryKey = s.getKey(e);
+ if (s.getHash(e) == hash && entryKey != null
+ && s.equalKeys(entryKey, key)) {
+ V entryValue = strategy.getValue(e);
+ // All entries following removed node can stay
+ // in list, but all preceding ones need to be
+ // cloned.
+ ++modCount;
+ E newFirst = s.getNext(e);
+ for (E p = first; p != e; p = s.getNext(p)) {
+ K pKey = s.getKey(p);
+ if (pKey != null) {
+ newFirst = s.copyEntry(pKey, p, newFirst);
+ } else {
+ // Key was reclaimed. Skip entry.
+ }
+ }
+ table.set(index, newFirst);
+ this.count = count; // write-volatile
+ return entryValue;
+ }
+ }
+
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ boolean remove(Object key, int hash, Object value) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ int count = this.count - 1;
+ AtomicReferenceArray<E> table = this.table;
+ int index = hash & (table.length() - 1);
+ E first = table.get(index);
+
+ for (E e = first; e != null; e = s.getNext(e)) {
+ K entryKey = s.getKey(e);
+ if (s.getHash(e) == hash && entryKey != null
+ && s.equalKeys(entryKey, key)) {
+ V entryValue = strategy.getValue(e);
+ if (value == entryValue || (value != null && entryValue != null
+ && s.equalValues(entryValue, value))) {
+ // All entries following removed node can stay
+ // in list, but all preceding ones need to be
+ // cloned.
+ ++modCount;
+ E newFirst = s.getNext(e);
+ for (E p = first; p != e; p = s.getNext(p)) {
+ K pKey = s.getKey(p);
+ if (pKey != null) {
+ newFirst = s.copyEntry(pKey, p, newFirst);
+ } else {
+ // Key was reclaimed. Skip entry.
+ }
+ }
+ table.set(index, newFirst);
+ this.count = count; // write-volatile
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ }
+ }
+
+ public boolean removeEntry(E entry, int hash, V value) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ int count = this.count - 1;
+ AtomicReferenceArray<E> table = this.table;
+ int index = hash & (table.length() - 1);
+ E first = table.get(index);
+
+ for (E e = first; e != null; e = s.getNext(e)) {
+ if (s.getHash(e) == hash && entry.equals(e)) {
+ V entryValue = s.getValue(e);
+ if (entryValue == value || (value != null
+ && s.equalValues(entryValue, value))) {
+ // All entries following removed node can stay
+ // in list, but all preceding ones need to be
+ // cloned.
+ ++modCount;
+ E newFirst = s.getNext(e);
+ for (E p = first; p != e; p = s.getNext(p)) {
+ K pKey = s.getKey(p);
+ if (pKey != null) {
+ newFirst = s.copyEntry(pKey, p, newFirst);
+ } else {
+ // Key was reclaimed. Skip entry.
+ }
+ }
+ table.set(index, newFirst);
+ this.count = count; // write-volatile
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ }
+ }
+
+ public boolean removeEntry(E entry, int hash) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ lock();
+ try {
+ int count = this.count - 1;
+ AtomicReferenceArray<E> table = this.table;
+ int index = hash & (table.length() - 1);
+ E first = table.get(index);
+
+ for (E e = first; e != null; e = s.getNext(e)) {
+ if (s.getHash(e) == hash && entry.equals(e)) {
+ // All entries following removed node can stay
+ // in list, but all preceding ones need to be
+ // cloned.
+ ++modCount;
+ E newFirst = s.getNext(e);
+ for (E p = first; p != e; p = s.getNext(p)) {
+ K pKey = s.getKey(p);
+ if (pKey != null) {
+ newFirst = s.copyEntry(pKey, p, newFirst);
+ } else {
+ // Key was reclaimed. Skip entry.
+ }
+ }
+ table.set(index, newFirst);
+ this.count = count; // write-volatile
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ }
+ }
+
+ void clear() {
+ if (count != 0) {
+ lock();
+ try {
+ AtomicReferenceArray<E> table = this.table;
+ for (int i = 0; i < table.length(); i++) {
+ table.set(i, null);
+ }
+ ++modCount;
+ count = 0; // write-volatile
+ } finally {
+ unlock();
+ }
+ }
+ }
+ }
+
+ /* ---------------- Public operations -------------- */
+
+ /**
+ * Returns {@code true} if this map contains no key-value mappings.
+ *
+ * @return {@code true} if this map contains no key-value mappings
+ */
+ public boolean isEmpty() {
+ final Segment[] segments = this.segments;
+ /*
+ * We keep track of per-segment modCounts to avoid ABA
+ * problems in which an element in one segment was added and
+ * in another removed during traversal, in which case the
+ * table was never actually empty at any point. Note the
+ * similar use of modCounts in the size() and containsValue()
+ * methods, which are the only other methods also susceptible
+ * to ABA problems.
+ */
+ int[] mc = new int[segments.length];
+ int mcsum = 0;
+ for (int i = 0; i < segments.length; ++i) {
+ if (segments[i].count != 0) {
+ return false;
+ } else {
+ mcsum += mc[i] = segments[i].modCount;
+ }
+ }
+ // If mcsum happens to be zero, then we know we got a snapshot
+ // before any modifications at all were made. This is
+ // probably common enough to bother tracking.
+ if (mcsum != 0) {
+ for (int i = 0; i < segments.length; ++i) {
+ if (segments[i].count != 0 ||
+ mc[i] != segments[i].modCount) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the number of key-value mappings in this map. If the map
+ * contains more than {@code Integer.MAX_VALUE} elements, returns
+ * {@code Integer.MAX_VALUE}.
+ *
+ * @return the number of key-value mappings in this map
+ */
+ public int size() {
+ final Segment[] segments = this.segments;
+ long sum = 0;
+ long check = 0;
+ int[] mc = new int[segments.length];
+ // Try a few times to get accurate count. On failure due to
+ // continuous async changes in table, resort to locking.
+ for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
+ check = 0;
+ sum = 0;
+ int mcsum = 0;
+ for (int i = 0; i < segments.length; ++i) {
+ sum += segments[i].count;
+ mcsum += mc[i] = segments[i].modCount;
+ }
+ if (mcsum != 0) {
+ for (int i = 0; i < segments.length; ++i) {
+ check += segments[i].count;
+ if (mc[i] != segments[i].modCount) {
+ check = -1; // force retry
+ break;
+ }
+ }
+ }
+ if (check == sum) {
+ break;
+ }
+ }
+ if (check != sum) { // Resort to locking all segments
+ sum = 0;
+ for (Segment segment : segments) {
+ segment.lock();
+ }
+ for (Segment segment : segments) {
+ sum += segment.count;
+ }
+ for (Segment segment : segments) {
+ segment.unlock();
+ }
+ }
+ if (sum > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ } else {
+ return (int) sum;
+ }
+ }
+
+ /**
+ * Returns the value to which the specified key is mapped, or {@code null}
+ * if this map contains no mapping for the key.
+ *
+ * <p>More formally, if this map contains a mapping from a key {@code k} to
+ * a value {@code v} such that {@code key.equals(k)}, then this method
+ * returns {@code v}; otherwise it returns {@code null}. (There can be at
+ * most one such mapping.)
+ *
+ * @throws NullPointerException if the specified key is null
+ */
+ public V get(Object key) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).get(key, hash);
+ }
+
+ /**
+ * Tests if the specified object is a key in this table.
+ *
+ * @param key possible key
+ * @return {@code true} if and only if the specified object is a key in
+ * this table, as determined by the {@code equals} method;
+ * {@code false} otherwise.
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean containsKey(Object key) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).containsKey(key, hash);
+ }
+
+ /**
+ * Returns {@code true} if this map maps one or more keys to the specified
+ * value. Note: This method requires a full internal traversal of the hash
+ * table, and so is much slower than method {@code containsKey}.
+ *
+ * @param value value whose presence in this map is to be tested
+ * @return {@code true} if this map maps one or more keys to the specified
+ * value
+ * @throws NullPointerException if the specified value is null
+ */
+ public boolean containsValue(Object value) {
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+
+ // See explanation of modCount use above
+
+ final Segment[] segments = this.segments;
+ int[] mc = new int[segments.length];
+
+ // Try a few times without locking
+ for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
+ int mcsum = 0;
+ for (int i = 0; i < segments.length; ++i) {
+ @SuppressWarnings("UnusedDeclaration")
+ int c = segments[i].count;
+ mcsum += mc[i] = segments[i].modCount;
+ if (segments[i].containsValue(value)) {
+ return true;
+ }
+ }
+ boolean cleanSweep = true;
+ if (mcsum != 0) {
+ for (int i = 0; i < segments.length; ++i) {
+ @SuppressWarnings("UnusedDeclaration")
+ int c = segments[i].count;
+ if (mc[i] != segments[i].modCount) {
+ cleanSweep = false;
+ break;
+ }
+ }
+ }
+ if (cleanSweep) {
+ return false;
+ }
+ }
+ // Resort to locking all segments
+ for (Segment segment : segments) {
+ segment.lock();
+ }
+ boolean found = false;
+ try {
+ for (Segment segment : segments) {
+ if (segment.containsValue(value)) {
+ found = true;
+ break;
+ }
+ }
+ } finally {
+ for (Segment segment : segments) {
+ segment.unlock();
+ }
+ }
+ return found;
+ }
+
+ /**
+ * Maps the specified key to the specified value in this table. Neither the
+ * key nor the value can be null.
+ *
+ * <p> The value can be retrieved by calling the {@code get} method with a
+ * key that is equal to the original key.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with {@code key}, or {@code null}
+ * if there was no mapping for {@code key}
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V put(K key, V value) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).put(key, hash, value, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return the previous value associated with the specified key, or
+ * {@code null} if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V putIfAbsent(K key, V value) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).put(key, hash, value, true);
+ }
+
+ /**
+ * Copies all of the mappings from the specified map to this one. These
+ * mappings replace any mappings that this map had for any of the keys
+ * currently in the specified map.
+ *
+ * @param m mappings to be stored in this map
+ */
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Entry<? extends K, ? extends V> e : m.entrySet()) {
+ put(e.getKey(), e.getValue());
+ }
+ }
+
+ /**
+ * Removes the key (and its corresponding value) from this map. This method
+ * does nothing if the key is not in the map.
+ *
+ * @param key the key that needs to be removed
+ * @return the previous value associated with {@code key}, or {@code null}
+ * if there was no mapping for {@code key}
+ * @throws NullPointerException if the specified key is null
+ */
+ public V remove(Object key) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).remove(key, hash);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean remove(Object key, Object value) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).remove(key, hash, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException if any of the arguments are null
+ */
+ public boolean replace(K key, V oldValue, V newValue) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ if (oldValue == null) {
+ throw new NullPointerException("oldValue");
+ }
+ if (newValue == null) {
+ throw new NullPointerException("newValue");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).replace(key, hash, oldValue, newValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return the previous value associated with the specified key, or
+ * {@code null} if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V replace(K key, V value) {
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+ int hash = hash(key);
+ return segmentFor(hash).replace(key, hash, value);
+ }
+
+ /**
+ * Removes all of the mappings from this map.
+ */
+ public void clear() {
+ for (Segment segment : segments) {
+ segment.clear();
+ }
+ }
+
+ Set<K> keySet;
+
+ /**
+ * Returns a {@link java.util.Set} view of the keys contained in this map.
+ * The set is backed by the map, so changes to the map are reflected in the
+ * set, and vice-versa. The set supports element removal, which removes the
+ * corresponding mapping from this map, via the {@code Iterator.remove},
+ * {@code Set.remove}, {@code removeAll}, {@code retainAll}, and
+ * {@code clear} operations. It does not support the {@code add} or
+ * {@code addAll} operations.
+ *
+ * <p>The view's {@code iterator} is a "weakly consistent" iterator that
+ * will never throw {@link java.util.ConcurrentModificationException}, and
+ * guarantees to traverse elements as they existed upon construction of the
+ * iterator, and may (but is not guaranteed to) reflect any modifications
+ * subsequent to construction.
+ */
+ public Set<K> keySet() {
+ Set<K> ks = keySet;
+ return (ks != null) ? ks : (keySet = new KeySet());
+ }
+
+ Collection<V> values;
+
+ /**
+ * Returns a {@link java.util.Collection} view of the values contained in
+ * this map. The collection is backed by the map, so changes to the map are
+ * reflected in the collection, and vice-versa. The collection supports
+ * element removal, which removes the corresponding mapping from this map,
+ * via the {@code Iterator.remove}, {@code Collection.remove},
+ * {@code removeAll}, {@code retainAll}, and {@code clear} operations. It
+ * does not support the {@code add} or {@code addAll} operations.
+ *
+ * <p>The view's {@code iterator} is a "weakly consistent" iterator that
+ * will never throw {@link java.util.ConcurrentModificationException}, and
+ * guarantees to traverse elements as they existed upon construction of the
+ * iterator, and may (but is not guaranteed to) reflect any modifications
+ * subsequent to construction.
+ */
+ public Collection<V> values() {
+ Collection<V> vs = values;
+ return (vs != null) ? vs : (values = new Values());
+ }
+
+ Set<Entry<K, V>> entrySet;
+
+ /**
+ * Returns a {@link java.util.Set} view of the mappings contained in this
+ * map. The set is backed by the map, so changes to the map are reflected in
+ * the set, and vice-versa. The set supports element removal, which removes
+ * the corresponding mapping from the map, via the {@code Iterator.remove},
+ * {@code Set.remove}, {@code removeAll}, {@code retainAll}, and
+ * {@code clear} operations. It does not support the {@code add} or
+ * {@code addAll} operations.
+ *
+ * <p>The view's {@code iterator} is a "weakly consistent" iterator that
+ * will never throw {@link java.util.ConcurrentModificationException}, and
+ * guarantees to traverse elements as they existed upon construction of the
+ * iterator, and may (but is not guaranteed to) reflect any modifications
+ * subsequent to construction.
+ */
+ public Set<Entry<K, V>> entrySet() {
+ Set<Entry<K, V>> es = entrySet;
+ return (es != null) ? es : (entrySet = new EntrySet());
+ }
+
+ /* ---------------- Iterator Support -------------- */
+
+ abstract class HashIterator {
+
+ int nextSegmentIndex;
+ int nextTableIndex;
+ AtomicReferenceArray<E> currentTable;
+ E nextEntry;
+ WriteThroughEntry nextExternal;
+ WriteThroughEntry lastReturned;
+
+ HashIterator() {
+ nextSegmentIndex = segments.length - 1;
+ nextTableIndex = -1;
+ advance();
+ }
+
+ public boolean hasMoreElements() {
+ return hasNext();
+ }
+
+ final void advance() {
+ nextExternal = null;
+
+ if (nextInChain()) {
+ return;
+ }
+
+ if (nextInTable()) {
+ return;
+ }
+
+ while (nextSegmentIndex >= 0) {
+ Segment seg = segments[nextSegmentIndex--];
+ if (seg.count != 0) {
+ currentTable = seg.table;
+ nextTableIndex = currentTable.length() - 1;
+ if (nextInTable()) {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds the next entry in the current chain. Returns true if an entry
+ * was found.
+ */
+ boolean nextInChain() {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ if (nextEntry != null) {
+ for (nextEntry = s.getNext(nextEntry); nextEntry != null;
+ nextEntry = s.getNext(nextEntry)) {
+ if (advanceTo(nextEntry)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Finds the next entry in the current table. Returns true if an entry
+ * was found.
+ */
+ boolean nextInTable() {
+ while (nextTableIndex >= 0) {
+ if ((nextEntry = currentTable.get(nextTableIndex--)) != null) {
+ if (advanceTo(nextEntry) || nextInChain()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Advances to the given entry. Returns true if the entry was valid,
+ * false if it should be skipped.
+ */
+ boolean advanceTo(E entry) {
+ Strategy<K, V, E> s = Impl.this.strategy;
+ K key = s.getKey(entry);
+ V value = s.getValue(entry);
+ if (key != null && value != null) {
+ nextExternal = new WriteThroughEntry(key, value);
+ return true;
+ } else {
+ // Skip partially reclaimed entry.
+ return false;
+ }
+ }
+
+ public boolean hasNext() {
+ return nextExternal != null;
+ }
+
+ WriteThroughEntry nextEntry() {
+ if (nextExternal == null) {
+ throw new NoSuchElementException();
+ }
+ lastReturned = nextExternal;
+ advance();
+ return lastReturned;
+ }
+
+ public void remove() {
+ if (lastReturned == null) {
+ throw new IllegalStateException();
+ }
+ Impl.this.remove(lastReturned.getKey());
+ lastReturned = null;
+ }
+ }
+
+ final class KeyIterator extends HashIterator implements Iterator<K> {
+
+ public K next() {
+ return super.nextEntry().getKey();
+ }
+ }
+
+ final class ValueIterator extends HashIterator implements Iterator<V> {
+
+ public V next() {
+ return super.nextEntry().getValue();
+ }
+ }
+
+ /**
+ * Custom Entry class used by EntryIterator.next(), that relays setValue
+ * changes to the underlying map.
+ */
+ final class WriteThroughEntry
+ extends SimpleEntry<K, V> {
+
+ WriteThroughEntry(K k, V v) {
+ super(k, v);
+ }
+
+ /**
+ * Set our entry's value and write through to the map. The value to
+ * return is somewhat arbitrary here. Since a WriteThroughEntry does not
+ * necessarily track asynchronous changes, the most recent "previous"
+ * value could be different from what we return (or could even have been
+ * removed in which case the put will re-establish). We do not and
+ * cannot guarantee more.
+ */
+ @Override
+ public V setValue(V value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ V v = super.setValue(value);
+ Impl.this.put(getKey(), value);
+ return v;
+ }
+ }
+
+ final class EntryIterator extends HashIterator
+ implements Iterator<Entry<K, V>> {
+
+ public Entry<K, V> next() {
+ return nextEntry();
+ }
+ }
+
+ final class KeySet extends AbstractSet<K> {
+
+ public Iterator<K> iterator() {
+ return new KeyIterator();
+ }
+
+ public int size() {
+ return Impl.this.size();
+ }
+
+ public boolean isEmpty() {
+ return Impl.this.isEmpty();
+ }
+
+ public boolean contains(Object o) {
+ return Impl.this.containsKey(o);
+ }
+
+ public boolean remove(Object o) {
+ return Impl.this.remove(o) != null;
+ }
+
+ public void clear() {
+ Impl.this.clear();
+ }
+ }
+
+ final class Values extends AbstractCollection<V> {
+
+ public Iterator<V> iterator() {
+ return new ValueIterator();
+ }
+
+ public int size() {
+ return Impl.this.size();
+ }
+
+ public boolean isEmpty() {
+ return Impl.this.isEmpty();
+ }
+
+ public boolean contains(Object o) {
+ return Impl.this.containsValue(o);
+ }
+
+ public void clear() {
+ Impl.this.clear();
+ }
+ }
+
+ final class EntrySet extends AbstractSet<Entry<K, V>> {
+
+ public Iterator<Entry<K, V>> iterator() {
+ return new EntryIterator();
+ }
+
+ public boolean contains(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+ Entry<?, ?> e = (Entry<?, ?>) o;
+ Object key = e.getKey();
+ if (key == null) {
+ return false;
+ }
+ V v = Impl.this.get(key);
+
+ return v != null && strategy.equalValues(v, e.getValue());
+ }
+
+ public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+ Entry<?, ?> e = (Entry<?, ?>) o;
+ Object key = e.getKey();
+ return key != null && Impl.this.remove(key, e.getValue());
+ }
+
+ public int size() {
+ return Impl.this.size();
+ }
+
+ public boolean isEmpty() {
+ return Impl.this.isEmpty();
+ }
+
+ public void clear() {
+ Impl.this.clear();
+ }
+ }
+
+ /* ---------------- Serialization Support -------------- */
+
+ private static final long serialVersionUID = 0;
+
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ out.writeInt(size());
+ out.writeFloat(loadFactor);
+ out.writeInt(segments.length); // concurrencyLevel
+ out.writeObject(strategy);
+ for (Entry<K, V> entry : entrySet()) {
+ out.writeObject(entry.getKey());
+ out.writeObject(entry.getValue());
+ }
+ out.writeObject(null); // terminate entries
+ }
+
+ /**
+ * Fields used during deserialization. We use a nested class so we don't
+ * load them until we need them. We need to use reflection to set final
+ * fields outside of the constructor.
+ */
+ static class Fields {
+
+ static final Field loadFactor = findField("loadFactor");
+ static final Field segmentShift = findField("segmentShift");
+ static final Field segmentMask = findField("segmentMask");
+ static final Field segments = findField("segments");
+ static final Field strategy = findField("strategy");
+
+ static Field findField(String name) {
+ try {
+ Field f = Impl.class.getDeclaredField(name);
+ f.setAccessible(true);
+ return f;
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ try {
+ int initialCapacity = in.readInt();
+ float loadFactor = in.readFloat();
+ int concurrencyLevel = in.readInt();
+ Strategy<K, V, E> strategy = (Strategy<K, V, E>) in.readObject();
+
+ Fields.loadFactor.set(this, loadFactor);
+
+ if (concurrencyLevel > MAX_SEGMENTS) {
+ concurrencyLevel = MAX_SEGMENTS;
+ }
+
+ // Find power-of-two sizes best matching arguments
+ int segmentShift = 0;
+ int segmentCount = 1;
+ while (segmentCount < concurrencyLevel) {
+ ++segmentShift;
+ segmentCount <<= 1;
+ }
+ Fields.segmentShift.set(this, 32 - segmentShift);
+ Fields.segmentMask.set(this, segmentCount - 1);
+ Fields.segments.set(this, newSegmentArray(segmentCount));
+
+ if (initialCapacity > MAXIMUM_CAPACITY) {
+ initialCapacity = MAXIMUM_CAPACITY;
+ }
+
+ int segmentCapacity = initialCapacity / segmentCount;
+ if (segmentCapacity * segmentCount < initialCapacity) {
+ ++segmentCapacity;
+ }
+
+ int segmentSize = 1;
+ while (segmentSize < segmentCapacity) {
+ segmentSize <<= 1;
+ }
+ for (int i = 0; i < this.segments.length; ++i) {
+ this.segments[i] = new Segment(segmentSize);
+ }
+
+ Fields.strategy.set(this, strategy);
+
+ while (true) {
+ K key = (K) in.readObject();
+ if (key == null) {
+ break; // terminator
+ }
+ V value = (V) in.readObject();
+ put(key, value);
+ }
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ static class ComputingImpl<K, V, E> extends Impl<K, V, E> {
+
+ static final long serialVersionUID = 0;
+
+ final ComputingStrategy<K, V, E> strategy;
+ final Function<? super K, ? extends V> computer;
+
+ /**
+ * Creates a new, empty map with the specified strategy, initial capacity,
+ * load factor and concurrency level.
+ */
+ ComputingImpl(ComputingStrategy<K, V, E> strategy, Builder builder,
+ Function<? super K, ? extends V> computer) {
+ super(strategy, builder);
+ this.strategy = strategy;
+ this.computer = computer;
+ }
+
+ @Override
+ public V get(Object k) {
+ /*
+ * This cast isn't safe, but we can rely on the fact that K is almost
+ * always passed to Map.get(), and tools like IDEs and Findbugs can
+ * catch situations where this isn't the case.
+ *
+ * The alternative is to add an overloaded method, but the chances of
+ * a user calling get() instead of the new API and the risks inherent
+ * in adding a new API outweigh this little hole.
+ */
+ @SuppressWarnings("unchecked")
+ K key = (K) k;
+
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+
+ int hash = hash(key);
+ Segment segment = segmentFor(hash);
+ outer: while (true) {
+ E entry = segment.getEntry(key, hash);
+ if (entry == null) {
+ boolean created = false;
+ segment.lock();
+ try {
+ // Try again--an entry could have materialized in the interim.
+ entry = segment.getEntry(key, hash);
+ if (entry == null) {
+ // Create a new entry.
+ created = true;
+ int count = segment.count;
+ if (count++ > segment.threshold) { // ensure capacity
+ segment.expand();
+ }
+ AtomicReferenceArray<E> table = segment.table;
+ int index = hash & (table.length() - 1);
+ E first = table.get(index);
+ ++segment.modCount;
+ entry = strategy.newEntry(key, hash, first);
+ table.set(index, entry);
+ segment.count = count; // write-volatile
+ }
+ } finally {
+ segment.unlock();
+ }
+
+ if (created) {
+ // This thread solely created the entry.
+ boolean success = false;
+ try {
+ V value = strategy.compute(key, entry, computer);
+ if (value == null) {
+ throw new NullPointerException(
+ "compute() returned null unexpectedly");
+ }
+ success = true;
+ return value;
+ } finally {
+ if (!success) {
+ segment.removeEntry(entry, hash);
+ }
+ }
+ }
+ }
+
+ // The entry already exists. Wait for the computation.
+ boolean interrupted = false;
+ try {
+ while (true) {
+ try {
+ V value = strategy.waitForValue(entry);
+ if (value == null) {
+ // Purge entry and try again.
+ segment.removeEntry(entry, hash);
+ continue outer;
+ }
+ return value;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+ }
+}
+
+class SimpleEntry<K, V> implements Map.Entry<K, V> {
+
+ final K key;
+ V value;
+
+ SimpleEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V v) {
+ V old = value;
+ value = v;
+ return old;
+ }
+
+ @Override
+ public int hashCode() {
+ return (getKey() == null ? 0 : getKey().hashCode()) ^
+ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry e = (Map.Entry) o;
+ return (getKey()==null ?
+ e.getKey()==null : getKey().equals(e.getKey())) &&
+ (getValue()==null ? e.getValue()==null : getValue().equals(e.getValue()));
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/inject/internal/ExpirationTimer.java b/src/com/google/inject/internal/ExpirationTimer.java
new file mode 100644
index 0000000..1a4b33f
--- /dev/null
+++ b/src/com/google/inject/internal/ExpirationTimer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.util.Timer;
+
+/**
+ * Timer used for entry expiration in MapMaker.
+ */
+class ExpirationTimer {
+ static Timer instance = new Timer(true);
+}
diff --git a/src/com/google/inject/internal/FailableCache.java b/src/com/google/inject/internal/FailableCache.java
index 3e3250f..3522b9d 100644
--- a/src/com/google/inject/internal/FailableCache.java
+++ b/src/com/google/inject/internal/FailableCache.java
@@ -16,6 +16,8 @@
package com.google.inject.internal;
+import java.util.Map;
+
/**
* Lazily creates (and caches) values for keys. If creating the value fails (with errors), an
* exception is thrown on retrieval.
@@ -24,18 +26,19 @@
*/
public abstract class FailableCache<K, V> {
- private final ReferenceCache<K, Object> delegate = new ReferenceCache<K, Object>() {
- protected final Object create(K key) {
- Errors errors = new Errors();
- V result = null;
- try {
- result = FailableCache.this.create(key, errors);
- } catch (ErrorsException e) {
- errors.merge(e.getErrors());
- }
- return errors.hasErrors() ? errors : result;
- }
- };
+ private final Map<K, Object> delegate = new MapMaker().makeComputingMap(
+ new Function<K, Object>() {
+ public Object apply(@Nullable K key) {
+ Errors errors = new Errors();
+ V result = null;
+ try {
+ result = FailableCache.this.create(key, errors);
+ } catch (ErrorsException e) {
+ errors.merge(e.getErrors());
+ }
+ return errors.hasErrors() ? errors : result;
+ }
+ });
protected abstract V create(K key, Errors errors) throws ErrorsException;
diff --git a/src/com/google/inject/internal/FinalizablePhantomReference.java b/src/com/google/inject/internal/FinalizablePhantomReference.java
new file mode 100644
index 0000000..ece0b9e
--- /dev/null
+++ b/src/com/google/inject/internal/FinalizablePhantomReference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.lang.ref.PhantomReference;
+
+/**
+ * Phantom reference with a {@code finalizeReferent()} method which a
+ * background thread invokes after the garbage collector reclaims the
+ * referent. This is a simpler alternative to using a {@link
+ * java.lang.ref.ReferenceQueue}.
+ *
+ * <p>Unlike a normal phantom reference, this reference will be cleared
+ * automatically.
+ *
+ * @author Bob Lee
+ */
+public abstract class FinalizablePhantomReference<T>
+ extends PhantomReference<T> implements FinalizableReference {
+
+ /**
+ * Constructs a new finalizable phantom reference.
+ *
+ * @param referent to phantom reference
+ * @param queue that should finalize the referent
+ */
+ protected FinalizablePhantomReference(T referent,
+ FinalizableReferenceQueue queue) {
+ super(referent, queue.queue);
+ }
+}
diff --git a/src/com/google/inject/internal/FinalizableReference.java b/src/com/google/inject/internal/FinalizableReference.java
index ee732a5..d037261 100644
--- a/src/com/google/inject/internal/FinalizableReference.java
+++ b/src/com/google/inject/internal/FinalizableReference.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2006 Google Inc.
+/*
+ * Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,13 @@
package com.google.inject.internal;
/**
- * Package-private interface implemented by references that have code to run
- * after garbage collection of their referents.
+ * Implemented by references that have code to run after garbage collection of
+ * their referents.
*
- * @author crazybob@google.com (Bob Lee)
+ * @see FinalizableReferenceQueue
+ * @author Bob Lee
*/
-interface FinalizableReference {
+public interface FinalizableReference {
/**
* Invoked on a background thread after the referent has been garbage
diff --git a/src/com/google/inject/internal/FinalizableReferenceQueue.java b/src/com/google/inject/internal/FinalizableReferenceQueue.java
index 6e2836e..3d95943 100644
--- a/src/com/google/inject/internal/FinalizableReferenceQueue.java
+++ b/src/com/google/inject/internal/FinalizableReferenceQueue.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2006 Google Inc.
+/*
+ * Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,61 +16,259 @@
package com.google.inject.internal;
-import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.io.IOException;
+import java.io.FileNotFoundException;
/**
- * Starts a background thread that cleans up after reclaimed referents.
+ * A reference queue with an associated background thread that dequeues
+ * references and invokes {@link FinalizableReference#finalizeReferent()} on
+ * them.
*
- * @author Bob Lee (crazybob@google.com)
+ * <p>Keep a strong reference to this object until all of the associated
+ * referents have been finalized. If this object is garbage collected earlier,
+ * the backing thread will not invoke {@code finalizeReferent()} on the
+ * remaining references.
+ *
+ * @author Bob Lee
*/
-class FinalizableReferenceQueue extends ReferenceQueue<Object> {
+public class FinalizableReferenceQueue {
- private static final Logger logger =
- Logger.getLogger(FinalizableReferenceQueue.class.getName());
+ /*
+ * The Finalizer thread keeps a phantom reference to this object. When the
+ * client (ReferenceMap, for example) no longer has a strong reference to
+ * this object, the garbage collector will reclaim it and enqueue the
+ * phantom reference. The enqueued reference will trigger the Finalizer to
+ * stop.
+ *
+ * If this library is loaded in the system class loader,
+ * FinalizableReferenceQueue can load Finalizer directly with no problems.
+ *
+ * If this library is loaded in an application class loader, it's important
+ * that Finalizer not have a strong reference back to the class loader.
+ * Otherwise, you could have a graph like this:
+ *
+ * Finalizer Thread
+ * runs instance of -> Finalizer.class
+ * loaded by -> Application class loader
+ * which loaded -> ReferenceMap.class
+ * which has a static -> FinalizableReferenceQueue instance
+ *
+ * Even if no other references to classes from the application class loader
+ * remain, the Finalizer thread keeps an indirect strong reference to the
+ * queue in ReferenceMap, which keeps the Finalizer running, and as a result,
+ * the application class loader can never be reclaimed.
+ *
+ * This means that dynamically loaded web applications and OSGi bundles can't
+ * be unloaded.
+ *
+ * If the library is loaded in an application class loader, we try to break
+ * the cycle by loading Finalizer in its own independent class loader:
+ *
+ * System class loader
+ * -> Application class loader
+ * -> ReferenceMap
+ * -> FinalizableReferenceQueue
+ * -> etc.
+ * -> Decoupled class loader
+ * -> Finalizer
+ *
+ * Now, Finalizer no longer keeps an indirect strong reference to the
+ * static FinalizableReferenceQueue field in ReferenceMap. The application
+ * class loader can be reclaimed at which point the Finalizer thread will
+ * stop and its decoupled class loader can also be reclaimed.
+ *
+ * If any of this fails along the way, we fall back to loading Finalizer
+ * directly in the application class loader.
+ */
- private FinalizableReferenceQueue() {}
+ private static final Logger logger
+ = Logger.getLogger(FinalizableReferenceQueue.class.getName());
- void cleanUp(Reference reference) {
- try {
- ((FinalizableReference) reference).finalizeReferent();
- } catch (Throwable t) {
- deliverBadNews(t);
- }
- }
+ private static final String FINALIZER_CLASS_NAME
+ = "com.google.inject.internal.Finalizer";
- void deliverBadNews(Throwable t) {
- logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
- }
-
- void start() {
- Thread thread = new Thread("FinalizableReferenceQueue") {
- public void run() {
- while (true) {
- try {
- cleanUp(remove());
- } catch (InterruptedException e) { /* ignore */ }
- }
- }
- };
- thread.setDaemon(true);
- thread.start();
- }
-
- static final ReferenceQueue<Object> instance = createAndStart();
-
- static FinalizableReferenceQueue createAndStart() {
- FinalizableReferenceQueue queue = new FinalizableReferenceQueue();
- queue.start();
- return queue;
+ /** Reference to Finalizer.startFinalizer(). */
+ private static final Method startFinalizer;
+ static {
+ Class<?> finalizer = loadFinalizer(
+ new SystemLoader(), new DecoupledLoader(), new DirectLoader());
+ startFinalizer = getStartFinalizer(finalizer);
}
/**
- * Gets instance.
+ * The actual reference queue that our background thread will poll.
*/
- public static ReferenceQueue<Object> getInstance() {
- return instance;
+ final ReferenceQueue<Object> queue;
+
+ /**
+ * Constructs a new queue.
+ */
+ @SuppressWarnings("unchecked")
+ public FinalizableReferenceQueue() {
+ // We could start the finalizer lazily, but I'd rather it blow up early.
+ try {
+ this.queue = (ReferenceQueue<Object>) startFinalizer.invoke(null,
+ FinalizableReference.class, this);
+ } catch (IllegalAccessException e) {
+ // Finalizer.startFinalizer() is public.
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Iterates through the given loaders until it finds one that can load
+ * Finalizer.
+ *
+ * @return Finalizer.class
+ */
+ private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
+ for (FinalizerLoader loader : loaders) {
+ Class<?> finalizer = loader.loadFinalizer();
+ if (finalizer != null) {
+ return finalizer;
+ }
+ }
+
+ throw new AssertionError();
+ }
+
+ /**
+ * Loads Finalizer.class.
+ */
+ interface FinalizerLoader {
+
+ /**
+ * Returns Finalizer.class or null if this loader shouldn't or can't load
+ * it.
+ *
+ * @throws SecurityException if we don't have the appropriate priveleges
+ */
+ Class<?> loadFinalizer();
+ }
+
+ /**
+ * Tries to load Finalizer from the system class loader. If Finalizer is
+ * in the system class path, we needn't create a separate loader.
+ */
+ static class SystemLoader implements FinalizerLoader {
+ public Class<?> loadFinalizer() {
+ ClassLoader systemLoader;
+ try {
+ systemLoader = ClassLoader.getSystemClassLoader();
+ } catch (SecurityException e) {
+ logger.info("Not allowed to access system class loader.");
+ return null;
+ }
+ if (systemLoader != null) {
+ try {
+ return systemLoader.loadClass(FINALIZER_CLASS_NAME);
+ } catch (ClassNotFoundException e) {
+ // Ignore. Finalizer is simply in a child class loader.
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Try to load Finalizer in its own class loader. If Finalizer's thread
+ * had a direct reference to our class loader (which could be that of
+ * a dynamically loaded web application or OSGi bundle), it would prevent
+ * our class loader from getting garbage collected.
+ */
+ static class DecoupledLoader implements FinalizerLoader {
+
+ private static final String LOADING_ERROR = "Could not load Finalizer in"
+ + " its own class loader. Loading Finalizer in the current class loader"
+ + " instead. As a result, you will not be able to garbage collect this"
+ + " class loader. To support reclaiming this class loader, either"
+ + " resolve the underlying issue, or move Google Collections to your"
+ + " system class path.";
+
+ public Class<?> loadFinalizer() {
+ try {
+ /*
+ * We use URLClassLoader because it's the only concrete class loader
+ * implementation in the JDK. If we used our own ClassLoader subclass,
+ * Finalizer would indirectly reference this class loader:
+ *
+ * Finalizer.class ->
+ * CustomClassLoader ->
+ * CustomClassLoader.class ->
+ * This class loader
+ *
+ * System class loader will (and must) be the parent.
+ */
+ ClassLoader finalizerLoader = newLoader(getBaseUrl());
+ return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
+ } catch (Exception e) {
+ logger.log(Level.WARNING, LOADING_ERROR, e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets URL for base of path containing Finalizer.class.
+ */
+ URL getBaseUrl() throws IOException {
+ // Find URL pointing to Finalizer.class file.
+ String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
+ URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
+ if (finalizerUrl == null) {
+ throw new FileNotFoundException(finalizerPath);
+ }
+
+ // Find URL pointing to base of class path.
+ String urlString = finalizerUrl.toString();
+ if (!urlString.endsWith(finalizerPath)) {
+ throw new IOException("Unsupported path style: " + urlString);
+ }
+ urlString = urlString.substring(0,
+ urlString.length() - finalizerPath.length());
+ return new URL(urlString);
+ }
+
+ /** Creates a class loader with the given base URL as its classpath. */
+ URLClassLoader newLoader(URL base) {
+ return new URLClassLoader(new URL[] { base });
+ }
+ }
+
+ /**
+ * Loads Finalizer directly using the current class loader. We won't be
+ * able to garbage collect this class loader, but at least the world
+ * doesn't end.
+ */
+ static class DirectLoader implements FinalizerLoader {
+ public Class<?> loadFinalizer() {
+ try {
+ return Class.forName(FINALIZER_CLASS_NAME);
+ } catch (ClassNotFoundException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ /**
+ * Looks up Finalizer.startFinalizer().
+ */
+ static Method getStartFinalizer(Class<?> finalizer) {
+ try {
+ return finalizer.getMethod("startFinalizer", Class.class, Object.class);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
}
}
+
diff --git a/src/com/google/inject/internal/FinalizableSoftReference.java b/src/com/google/inject/internal/FinalizableSoftReference.java
index bd6eae7..e69d7b9 100644
--- a/src/com/google/inject/internal/FinalizableSoftReference.java
+++ b/src/com/google/inject/internal/FinalizableSoftReference.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2006 Google Inc.
+/*
+ * Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,17 +19,23 @@
import java.lang.ref.SoftReference;
/**
- * Soft reference with a {@link FinalizableReference#finalizeReferent()} method
- * which a background thread invokes after the garbage collector reclaims the
- * referent. This is a simpler alternative to using a
- * {@link java.lang.ref.ReferenceQueue}.
+ * Soft reference with a {@code finalizeReferent()} method which a background
+ * thread invokes after the garbage collector reclaims the referent. This is a
+ * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}.
*
- * @author crazybob@google.com (Bob Lee)
+ * @author Bob Lee
*/
public abstract class FinalizableSoftReference<T> extends SoftReference<T>
implements FinalizableReference {
- protected FinalizableSoftReference(T referent) {
- super(referent, FinalizableReferenceQueue.getInstance());
+ /**
+ * Consructs a new finalizable soft reference.
+ *
+ * @param referent to softly reference
+ * @param queue that should finalize the referent
+ */
+ protected FinalizableSoftReference(T referent,
+ FinalizableReferenceQueue queue) {
+ super(referent, queue.queue);
}
}
diff --git a/src/com/google/inject/internal/FinalizableWeakReference.java b/src/com/google/inject/internal/FinalizableWeakReference.java
index 064dcad..0615f60 100644
--- a/src/com/google/inject/internal/FinalizableWeakReference.java
+++ b/src/com/google/inject/internal/FinalizableWeakReference.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2006 Google Inc.
+/*
+ * Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,17 +19,23 @@
import java.lang.ref.WeakReference;
/**
- * Weak reference with a {@link FinalizableReference#finalizeReferent()} method
- * which a background thread invokes after the garbage collector reclaims the
- * referent. This is a simpler alternative to using a
- * {@link java.lang.ref.ReferenceQueue}.
+ * Weak reference with a {@code finalizeReferent()} method which a background
+ * thread invokes after the garbage collector reclaims the referent. This is a
+ * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}.
*
- * @author crazybob@google.com (Bob Lee)
+ * @author Bob Lee
*/
public abstract class FinalizableWeakReference<T> extends WeakReference<T>
implements FinalizableReference {
- protected FinalizableWeakReference(T referent) {
- super(referent, FinalizableReferenceQueue.getInstance());
+ /**
+ * Consructs a new finalizable weak reference.
+ *
+ * @param referent to weakly reference
+ * @param queue that should finalize the referent
+ */
+ protected FinalizableWeakReference(T referent,
+ FinalizableReferenceQueue queue) {
+ super(referent, queue.queue);
}
}
diff --git a/src/com/google/inject/internal/Finalizer.java b/src/com/google/inject/internal/Finalizer.java
new file mode 100644
index 0000000..0355894
--- /dev/null
+++ b/src/com/google/inject/internal/Finalizer.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.PhantomReference;
+import java.lang.reflect.Method;
+
+/**
+ * Thread that finalizes referents. All references should implement
+ * {@code com.google.inject.internal.FinalizableReference}.
+ *
+ * <p>While this class is public, we consider it to be *internal* and not part
+ * of our published API. It is public so we can access it reflectively across
+ * class loaders in secure environments.
+ *
+ * <p>This class can't depend on other Google Collections code. If we were
+ * to load this class in the same class loader as the rest of
+ * Google Collections, this thread would keep an indirect strong reference
+ * to the class loader and prevent it from being garbage collected. This
+ * poses a problem for environments where you want to throw away the class
+ * loader. For example, dynamically reloading a web application or unloading
+ * an OSGi bundle.
+ *
+ * <p>{@code com.google.inject.internal.FinalizableReferenceQueue} loads this class
+ * in its own class loader. That way, this class doesn't prevent the main
+ * class loader from getting garbage collected, and this class can detect when
+ * the main class loader has been garbage collected and stop itself.
+ */
+public class Finalizer extends Thread {
+
+ private static final Logger logger
+ = Logger.getLogger(Finalizer.class.getName());
+
+ /** Name of FinalizableReference.class. */
+ private static final String FINALIZABLE_REFERENCE
+ = "com.google.inject.internal.FinalizableReference";
+
+ /**
+ * Starts the Finalizer thread. FinalizableReferenceQueue calls this method
+ * reflectively.
+ *
+ * @param finalizableReferenceClass FinalizableReference.class
+ * @param frq reference to instance of FinalizableReferenceQueue that started
+ * this thread
+ * @return ReferenceQueue which Finalizer will poll
+ */
+ public static ReferenceQueue<Object> startFinalizer(
+ Class<?> finalizableReferenceClass, Object frq) {
+ /*
+ * We use FinalizableReference.class for two things:
+ *
+ * 1) To invoke FinalizableReference.finalizeReferent()
+ *
+ * 2) To detect when FinalizableReference's class loader has to be garbage
+ * collected, at which point, Finalizer can stop running
+ */
+ if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE)) {
+ throw new IllegalArgumentException(
+ "Expected " + FINALIZABLE_REFERENCE + ".");
+ }
+
+ Finalizer finalizer = new Finalizer(finalizableReferenceClass, frq);
+ finalizer.start();
+ return finalizer.queue;
+ }
+
+ private final WeakReference<Class<?>> finalizableReferenceClassReference;
+ private final PhantomReference<Object> frqReference;
+ private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
+
+ /** Constructs a new finalizer thread. */
+ private Finalizer(Class<?> finalizableReferenceClass, Object frq) {
+ super(Finalizer.class.getName());
+
+ this.finalizableReferenceClassReference
+ = new WeakReference<Class<?>>(finalizableReferenceClass);
+
+ // Keep track of the FRQ that started us so we know when to stop.
+ this.frqReference = new PhantomReference<Object>(frq, queue);
+
+ setDaemon(true);
+
+ // TODO: Priority?
+ }
+
+ /**
+ * Loops continuously, pulling references off the queue and cleaning them up.
+ */
+ @SuppressWarnings("InfiniteLoopStatement")
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ try {
+ cleanUp(queue.remove());
+ } catch (InterruptedException e) { /* ignore */ }
+ }
+ } catch (ShutDown shutDown) { /* ignore */ }
+ }
+
+ /**
+ * Cleans up a single reference. Catches and logs all throwables.
+ */
+ private void cleanUp(Reference<?> reference) throws ShutDown {
+ Method finalizeReferentMethod = getFinalizeReferentMethod();
+ do {
+ /*
+ * This is for the benefit of phantom references. Weak and soft
+ * references will have already been cleared by this point.
+ */
+ reference.clear();
+
+ if (reference == frqReference) {
+ /*
+ * The client no longer has a reference to the
+ * FinalizableReferenceQueue. We can stop.
+ */
+ throw new ShutDown();
+ }
+
+ try {
+ finalizeReferentMethod.invoke(reference);
+ } catch (Throwable t) {
+ logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
+ }
+
+ /*
+ * Loop as long as we have references available so as not to waste
+ * CPU looking up the Method over and over again.
+ */
+ } while ((reference = queue.poll()) != null);
+ }
+
+ /**
+ * Looks up FinalizableReference.finalizeReferent() method.
+ */
+ private Method getFinalizeReferentMethod() throws ShutDown {
+ Class<?> finalizableReferenceClass
+ = finalizableReferenceClassReference.get();
+ if (finalizableReferenceClass == null) {
+ /*
+ * FinalizableReference's class loader was reclaimed. While there's a
+ * chance that other finalizable references could be enqueued
+ * subsequently (at which point the class loader would be resurrected
+ * by virtue of us having a strong reference to it), we should pretty
+ * much just shut down and make sure we don't keep it alive any longer
+ * than necessary.
+ */
+ throw new ShutDown();
+ }
+ try {
+ return finalizableReferenceClass.getMethod("finalizeReferent");
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Indicates that it's time to shut down the Finalizer. */
+ @SuppressWarnings("serial") // Never serialized or thrown out of this class.
+ private static class ShutDown extends Exception { }
+}
diff --git a/src/com/google/inject/internal/Function.java b/src/com/google/inject/internal/Function.java
index 738fa3e..f6e9893 100644
--- a/src/com/google/inject/internal/Function.java
+++ b/src/com/google/inject/internal/Function.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2006 Google Inc.
+/*
+ * Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,28 +17,48 @@
package com.google.inject.internal;
/**
- * A Function provides a transformation on an object and returns the resulting
- * object. For example, a {@code StringToIntegerFunction} may implement
- * <code>Function<String,Integer></code> and transform integers in String
- * format to Integer format.
+ * A transformation from one object to another. For example, a
+ * {@code StringToIntegerFunction} may implement
+ * <code>Function<String,Integer></code> and transform integers in
+ * {@code String} format to {@code Integer} format.
*
- * <p>The transformation on the from object does not necessarily result in
+ * <p>The transformation on the source object does not necessarily result in
* an object of a different type. For example, a
- * {@code FarenheitToCelciusFunction} may implement
+ * {@code FarenheitToCelsiusFunction} may implement
* <code>Function<Float,Float></code>.
*
- * <p>Implementors of Function which may cause side effects upon evaluation are
- * strongly encouraged to state this fact clearly in their API documentation.
+ * <p>Implementations which may cause side effects upon evaluation are strongly
+ * encouraged to state this fact clearly in their API documentation.
+ *
+ * <p>This interface may be used with the Google Web Toolkit (GWT).
+ *
+ * @param <F> the type of the function input
+ * @param <T> the type of the function output
+ * @author Kevin Bourrillion
+ * @author Scott Bonneau
*/
-public interface Function<F,T> {
+public interface Function<F, T> {
/**
* Applies the function to an object of type {@code F}, resulting in an object
* of type {@code T}. Note that types {@code F} and {@code T} may or may not
* be the same.
- *
- * @param from The from object.
- * @return The resulting object.
+ *
+ * @param from the source object
+ * @return the resulting object
*/
- T apply(F from);
+ T apply(@Nullable F from);
+
+ /**
+ * Indicates whether some other object is equal to this {@code Function}.
+ * This method can return {@code true} <i>only</i> if the specified object is
+ * also a {@code Function} and, for every input object {@code o}, it returns
+ * exactly the same value. Thus, {@code function1.equals(function2)} implies
+ * that either {@code function1.apply(o)} and {@code function2.apply(o)} are
+ * both null, or {@code function1.apply(o).equals(function2.apply(o))}.
+ *
+ * <p>Note that it is always safe <em>not</em> to override
+ * {@link Object#equals}.
+ */
+ boolean equals(@Nullable Object obj);
}
diff --git a/src/com/google/inject/internal/MapMaker.java b/src/com/google/inject/internal/MapMaker.java
new file mode 100644
index 0000000..71ac824
--- /dev/null
+++ b/src/com/google/inject/internal/MapMaker.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import com.google.inject.internal.FinalizableReferenceQueue;
+import com.google.inject.internal.FinalizableSoftReference;
+import com.google.inject.internal.FinalizableWeakReference;
+import com.google.inject.internal.Function;
+import com.google.inject.internal.CustomConcurrentHashMap.ComputingStrategy;
+import com.google.inject.internal.CustomConcurrentHashMap.Internals;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ConcurrentMap} builder, providing any combination of these
+ * features: {@linkplain SoftReference soft} or {@linkplain WeakReference
+ * weak} keys, soft or weak values, timed expiration, and on-demand
+ * computation of values. Usage example: <pre> {@code
+ *
+ * ConcurrentMap<Key, Graph> graphs = new MapMaker()
+ * .concurrencyLevel(32)
+ * .softKeys()
+ * .weakValues()
+ * .expiration(30, TimeUnit.MINUTES)
+ * .makeComputingMap(
+ * new Function<Key, Graph>() {
+ * public Graph apply(Key key) {
+ * return createExpensiveGraph(key);
+ * }
+ * });}</pre>
+ *
+ * These features are all optional; {@code new MapMaker().makeMap()}
+ * returns a valid concurrent map that behaves exactly like a
+ * {@link ConcurrentHashMap}.
+ *
+ * The returned map is implemented as a hash table with similar performance
+ * characteristics to {@link ConcurrentHashMap}. It supports all optional
+ * operations of the {@code ConcurrentMap} interface. It does not permit
+ * null keys or values. It is serializable; however, serializing a map that
+ * uses soft or weak references can give unpredictable results.
+ *
+ * <p><b>Note:</b> by default, the returned map uses equality comparisons
+ * (the {@link Object#equals(Object) equals} method) to determine equality
+ * for keys or values. However, if {@link #weakKeys()} or {@link
+ * #softKeys()} was specified, the map uses identity ({@code ==})
+ * comparisons instead for keys. Likewise, if {@link #weakValues()} or
+ * {@link #softValues()} was specified, the map uses identity comparisons
+ * for values.
+ *
+ * <p>The returned map has <i>weakly consistent iteration</i>: an iterator
+ * over one of the map's view collections may reflect some, all or none of
+ * the changes made to the map after the iterator was created.
+ *
+ * <p>An entry whose key or value is reclaimed by the garbage collector
+ * immediately disappears from the map. (If the default settings of strong
+ * keys and strong values are used, this will never happen.) The client can
+ * never observe a partially-reclaimed entry. Any {@link Map.Entry}
+ * instance retrieved from the map's {@linkplain Map#entrySet() entry set}
+ * is snapshot of that entry's state at the time of retrieval.
+ *
+ * <p>{@code new MapMaker().weakKeys().makeMap()} can almost always be
+ * used as a drop-in replacement for {@link java.util.WeakHashMap}, adding
+ * concurrency, asynchronous cleanup, identity-based equality for keys, and
+ * great flexibility.
+ *
+ * @author Bob Lee
+ * @author Kevin Bourrillion
+ */
+public final class MapMaker {
+ private Strength keyStrength = Strength.STRONG;
+ private Strength valueStrength = Strength.STRONG;
+ private long expirationNanos = 0;
+ private boolean useCustomMap;
+ private final CustomConcurrentHashMap.Builder builder
+ = new CustomConcurrentHashMap.Builder();
+
+ /**
+ * Constructs a new {@code MapMaker} instance with default settings,
+ * including strong keys, strong values, and no automatic expiration.
+ */
+ public MapMaker() {}
+
+ /**
+ * Sets a custom initial capacity (defaults to 16). Resizing this or
+ * any other kind of hash table is a relatively slow operation, so,
+ * when possible, it is a good idea to provide estimates of expected
+ * table sizes.
+ *
+ * @throws IllegalArgumentException if {@code initialCapacity} is
+ * negative
+ * @throws IllegalStateException if an initial capacity was already set
+ * (TODO: make that true)
+ */
+ public MapMaker initialCapacity(int initialCapacity) {
+ builder.initialCapacity(initialCapacity);
+ return this;
+ }
+
+ /**
+ * Sets a custom load factor (defaults to 0.75).
+ *
+ * @throws IllegalArgumentException if {@code loadFactor} is
+ * nonpositive
+ * @throws IllegalStateException if a load factor was already set
+ * (TODO: make that true)
+ */
+ public MapMaker loadFactor(float loadFactor) {
+ builder.loadFactor(loadFactor);
+ return this;
+ }
+
+ /**C
+ * Guides the allowed concurrency among update operations. Used as a
+ * hint for internal sizing. The table is internally partitioned to try
+ * to permit the indicated number of concurrent updates without
+ * contention. Because placement in hash tables is essentially random,
+ * the actual concurrency will vary. Ideally, you should choose a value
+ * to accommodate as many threads as will ever concurrently modify the
+ * table. Using a significantly higher value than you need can waste
+ * space and time, and a significantly lower value can lead to thread
+ * contention. But overestimates and underestimates within an order of
+ * magnitude do not usually have much noticeable impact. A value of one
+ * is appropriate when it is known that only one thread will modify and
+ * all others will only read. Defaults to 16.
+ *
+ * @throws IllegalArgumentException if {@code concurrencyLevel} is
+ * nonpositive
+ * @throws IllegalStateException if a concurrency level was already set
+ * (TODO: make that true)
+ */
+ public MapMaker concurrencyLevel(int concurrencyLevel) {
+ builder.concurrencyLevel(concurrencyLevel);
+ return this;
+ }
+
+ /**
+ * Specifies that each key (not value) stored in the map should be
+ * wrapped in a {@link WeakReference} (by default, strong references
+ * are used).
+ *
+ * @throws IllegalStateException if the key strength was already set
+ */
+ public MapMaker weakKeys() {
+ return setKeyStrength(Strength.WEAK);
+ }
+
+ /**
+ * Specifies that each key (not value) stored in the map should be
+ * wrapped in a {@link SoftReference} (by default, strong references
+ * are used).
+ *
+ * @throws IllegalStateException if the key strength was already set
+ */
+ public MapMaker softKeys() {
+ return setKeyStrength(Strength.SOFT);
+ }
+
+ private MapMaker setKeyStrength(Strength strength) {
+ if (keyStrength != Strength.STRONG) {
+ throw new IllegalStateException("Key strength was already set to "
+ + keyStrength + ".");
+ }
+ keyStrength = strength;
+ useCustomMap = true;
+ return this;
+ }
+
+ /**
+ * Specifies that each value (not key) stored in the map should be
+ * wrapped in a {@link WeakReference} (by default, strong references
+ * are used).
+ *
+ * @throws IllegalStateException if the key strength was already set
+ */
+ public MapMaker weakValues() {
+ return setValueStrength(Strength.WEAK);
+ }
+
+ /**
+ * Specifies that each value (not key) stored in the map should be
+ * wrapped in a {@link SoftReference} (by default, strong references
+ * are used).
+ *
+ * @throws IllegalStateException if the value strength was already set
+ */
+ public MapMaker softValues() {
+ return setValueStrength(Strength.SOFT);
+ }
+
+ private MapMaker setValueStrength(Strength strength) {
+ if (valueStrength != Strength.STRONG) {
+ throw new IllegalStateException("Value strength was already set to "
+ + valueStrength + ".");
+ }
+ valueStrength = strength;
+ useCustomMap = true;
+ return this;
+ }
+
+ /**
+ * Specifies that each entry should be automatically removed from the
+ * map once a fixed duration has passed since the entry's creation.
+ *
+ * @param duration the length of time after an entry is created that it
+ * should be automatically removed
+ * @param unit the unit that {@code duration} is expressed in
+ * @throws IllegalArgumentException if {@code duration} is not positive
+ * @throws IllegalStateException if the expiration time was already set
+ */
+ public MapMaker expiration(long duration, TimeUnit unit) {
+ if (expirationNanos != 0) {
+ throw new IllegalStateException("expiration time of "
+ + expirationNanos + " ns was already set");
+ }
+ if (duration <= 0) {
+ throw new IllegalArgumentException("invalid duration: " + duration);
+ }
+ this.expirationNanos = unit.toNanos(duration);
+ useCustomMap = true;
+ return this;
+ }
+
+ /**
+ * Builds the final map, without on-demand computation of values.
+ *
+ * @param <K> the type of keys to be stored in the returned map
+ * @param <V> the type of values to be stored in the returned map
+ * @return a concurrent map having the requested features
+ */
+ public <K, V> ConcurrentMap<K, V> makeMap() {
+ return useCustomMap
+ ? new StrategyImpl<K, V>(this).map
+ : new ConcurrentHashMap<K, V>(builder.initialCapacity,
+ builder.loadFactor, builder.concurrencyLevel);
+ }
+
+ // TODO: Clone the referenced doc here after things stabilize more so
+ // we don't expose this implementation detail.
+ /**
+ * See {@link CustomConcurrentHashMap.Builder#buildComputingMap(
+ * CustomConcurrentHashMap.ComputingStrategy,
+ * com.google.inject.internal.Function)}.
+ *
+ * <p>If {@link java.util.Map#put} is called before a computation
+ * completes, other threads waiting on the computation will wake up and
+ * return the put value up until the computation completes, at which
+ * point the computation result will overwrite the value from the
+ * {@code put} in the map.
+ */
+ public <K, V> ConcurrentMap<K, V> makeComputingMap(
+ Function<? super K, ? extends V> computer) {
+ return new StrategyImpl<K, V>(this, computer).map;
+ }
+
+ // Remainder of this file is private implementation details
+
+ private enum Strength {
+ WEAK {
+ boolean equal(Object a, Object b) {
+ return a == b;
+ }
+ int hash(Object o) {
+ return System.identityHashCode(o);
+ }
+ <K, V> ValueReference<K, V> referenceValue(
+ ReferenceEntry<K, V> entry, V value) {
+ return new WeakValueReference<K, V>(value, entry);
+ }
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash, ReferenceEntry<K, V> next) {
+ return (next == null)
+ ? new WeakEntry<K, V>(internals, key, hash)
+ : new LinkedWeakEntry<K, V>(internals, key, hash, next);
+ }
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ K key, ReferenceEntry<K, V> original,
+ ReferenceEntry<K, V> newNext) {
+ WeakEntry<K, V> from = (WeakEntry<K, V>) original;
+ return (newNext == null)
+ ? new WeakEntry<K, V>(from.internals, key, from.hash)
+ : new LinkedWeakEntry<K, V>(
+ from.internals, key, from.hash, newNext);
+ }
+ },
+
+ SOFT {
+ boolean equal(Object a, Object b) {
+ return a == b;
+ }
+ int hash(Object o) {
+ return System.identityHashCode(o);
+ }
+ <K, V> ValueReference<K, V> referenceValue(
+ ReferenceEntry<K, V> entry, V value) {
+ return new SoftValueReference<K, V>(value, entry);
+ }
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash, ReferenceEntry<K, V> next) {
+ return (next == null)
+ ? new SoftEntry<K, V>(internals, key, hash)
+ : new LinkedSoftEntry<K, V>(internals, key, hash, next);
+ }
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ K key, ReferenceEntry<K, V> original,
+ ReferenceEntry<K, V> newNext) {
+ SoftEntry<K, V> from = (SoftEntry<K, V>) original;
+ return (newNext == null)
+ ? new SoftEntry<K, V>(from.internals, key, from.hash)
+ : new LinkedSoftEntry<K, V>(
+ from.internals, key, from.hash, newNext);
+ }
+ },
+
+ STRONG {
+ boolean equal(Object a, Object b) {
+ return a.equals(b);
+ }
+ int hash(Object o) {
+ return o.hashCode();
+ }
+ <K, V> ValueReference<K, V> referenceValue(
+ ReferenceEntry<K, V> entry, V value) {
+ return new StrongValueReference<K, V>(value);
+ }
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash, ReferenceEntry<K, V> next) {
+ return (next == null)
+ ? new StrongEntry<K, V>(internals, key, hash)
+ : new LinkedStrongEntry<K, V>(
+ internals, key, hash, next);
+ }
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ K key, ReferenceEntry<K, V> original,
+ ReferenceEntry<K, V> newNext) {
+ StrongEntry<K, V> from = (StrongEntry<K, V>) original;
+ return (newNext == null)
+ ? new StrongEntry<K, V>(from.internals, key, from.hash)
+ : new LinkedStrongEntry<K, V>(
+ from.internals, key, from.hash, newNext);
+ }
+ };
+
+ /**
+ * Determines if two keys or values are equal according to this
+ * strength strategy.
+ */
+ abstract boolean equal(Object a, Object b);
+
+ /**
+ * Hashes a key according to this strategy.
+ */
+ abstract int hash(Object o);
+
+ /**
+ * Creates a reference for the given value according to this value
+ * strength.
+ */
+ abstract <K, V> ValueReference<K, V> referenceValue(
+ ReferenceEntry<K, V> entry, V value);
+
+ /**
+ * Creates a new entry based on the current key strength.
+ */
+ abstract <K, V> ReferenceEntry<K, V> newEntry(
+ Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash, ReferenceEntry<K, V> next);
+
+ /**
+ * Creates a new entry and copies the value and other state from an
+ * existing entry.
+ */
+ abstract <K, V> ReferenceEntry<K, V> copyEntry(K key,
+ ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext);
+ }
+
+ private static class StrategyImpl<K, V> implements Serializable,
+ ComputingStrategy<K, V, ReferenceEntry<K, V>> {
+ final Strength keyStrength;
+ final Strength valueStrength;
+ final ConcurrentMap<K, V> map;
+ final long expirationNanos;
+ Internals<K, V, ReferenceEntry<K, V>> internals;
+
+ StrategyImpl(MapMaker maker) {
+ this.keyStrength = maker.keyStrength;
+ this.valueStrength = maker.valueStrength;
+ this.expirationNanos = maker.expirationNanos;
+
+ map = maker.builder.buildMap(this);
+ }
+
+ StrategyImpl(
+ MapMaker maker, Function<? super K, ? extends V> computer) {
+ this.keyStrength = maker.keyStrength;
+ this.valueStrength = maker.valueStrength;
+ this.expirationNanos = maker.expirationNanos;
+
+ map = maker.builder.buildComputingMap(this, computer);
+ }
+
+ public void setValue(ReferenceEntry<K, V> entry, V value) {
+ setValueReference(
+ entry, valueStrength.referenceValue(entry, value));
+ if (expirationNanos > 0) {
+ scheduleRemoval(entry.getKey(), value);
+ }
+ }
+
+ void scheduleRemoval(K key, V value) {
+ /*
+ * TODO: Keep weak reference to map, too. Build a priority
+ * queue out of the entries themselves instead of creating a
+ * task per entry. Then, we could have one recurring task per
+ * map (which would clean the entire map and then reschedule
+ * itself depending upon when the next expiration comes). We
+ * also want to avoid removing an entry prematurely if the
+ * entry was set to the same value again.
+ */
+ final WeakReference<K> keyReference = new WeakReference<K>(key);
+ final WeakReference<V> valueReference = new WeakReference<V>(value);
+ ExpirationTimer.instance.schedule(
+ new TimerTask() {
+ public void run() {
+ K key = keyReference.get();
+ if (key != null) {
+ // Remove if the value is still the same.
+ map.remove(key, valueReference.get());
+ }
+ }
+ }, TimeUnit.NANOSECONDS.toMillis(expirationNanos));
+ }
+
+ public boolean equalKeys(K a, Object b) {
+ return keyStrength.equal(a, b);
+ }
+
+ public boolean equalValues(V a, Object b) {
+ return valueStrength.equal(a, b);
+ }
+
+ public int hashKey(Object key) {
+ return keyStrength.hash(key);
+ }
+
+ public K getKey(ReferenceEntry<K, V> entry) {
+ return entry.getKey();
+ }
+
+ public int getHash(ReferenceEntry entry) {
+ return entry.getHash();
+ }
+
+ public ReferenceEntry<K, V> newEntry(
+ K key, int hash, ReferenceEntry<K, V> next) {
+ return keyStrength.newEntry(internals, key, hash, next);
+ }
+
+ public ReferenceEntry<K, V> copyEntry(K key,
+ ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ValueReference<K, V> valueReference = original.getValueReference();
+ if (valueReference == COMPUTING) {
+ ReferenceEntry<K, V> newEntry
+ = newEntry(key, original.getHash(), newNext);
+ newEntry.setValueReference(
+ new FutureValueReference(original, newEntry));
+ return newEntry;
+ } else {
+ ReferenceEntry<K, V> newEntry
+ = newEntry(key, original.getHash(), newNext);
+ newEntry.setValueReference(valueReference.copyFor(newEntry));
+ return newEntry;
+ }
+ }
+
+ /**
+ * Waits for a computation to complete. Returns the result of the
+ * computation or null if none was available.
+ */
+ public V waitForValue(ReferenceEntry<K, V> entry)
+ throws InterruptedException {
+ ValueReference<K, V> valueReference = entry.getValueReference();
+ if (valueReference == COMPUTING) {
+ synchronized (entry) {
+ while ((valueReference = entry.getValueReference())
+ == COMPUTING) {
+ entry.wait();
+ }
+ }
+ }
+ return valueReference.waitForValue();
+ }
+
+ /**
+ * Used by CustomConcurrentHashMap to retrieve values. Returns null
+ * instead of blocking or throwing an exception.
+ */
+ public V getValue(ReferenceEntry<K, V> entry) {
+ ValueReference<K, V> valueReference = entry.getValueReference();
+ return valueReference.get();
+ }
+
+ public V compute(K key, final ReferenceEntry<K, V> entry,
+ Function<? super K, ? extends V> computer) {
+ V value;
+ try {
+ value = computer.apply(key);
+ } catch (Throwable t) {
+ setValueReference(
+ entry, new ComputationExceptionReference<K, V>(t));
+ throw new ComputationException(t);
+ }
+
+ if (value == null) {
+ String message
+ = computer + " returned null for key " + key + ".";
+ setValueReference(
+ entry, new NullOutputExceptionReference<K, V>(message));
+ throw new NullOutputException(message);
+ } else {
+ setValue(entry, value);
+ }
+ return value;
+ }
+
+ /**
+ * Sets the value reference on an entry and notifies waiting
+ * threads.
+ */
+ void setValueReference(ReferenceEntry<K, V> entry,
+ ValueReference<K, V> valueReference) {
+ boolean notifyOthers = (entry.getValueReference() == COMPUTING);
+ entry.setValueReference(valueReference);
+ if (notifyOthers) {
+ synchronized (entry) {
+ entry.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Points to an old entry where a value is being computed. Used to
+ * support non-blocking copying of entries during table expansion,
+ * removals, etc.
+ */
+ private class FutureValueReference implements ValueReference<K, V> {
+ final ReferenceEntry<K, V> original;
+ final ReferenceEntry<K, V> newEntry;
+
+ FutureValueReference(
+ ReferenceEntry<K, V> original, ReferenceEntry<K, V> newEntry) {
+ this.original = original;
+ this.newEntry = newEntry;
+ }
+
+ public V get() {
+ boolean success = false;
+ try {
+ V value = original.getValueReference().get();
+ success = true;
+ return value;
+ } finally {
+ if (!success) {
+ removeEntry();
+ }
+ }
+ }
+
+ public ValueReference<K, V> copyFor(ReferenceEntry<K, V> entry) {
+ return new FutureValueReference(original, entry);
+ }
+
+ public V waitForValue() throws InterruptedException {
+ boolean success = false;
+ try {
+ // assert that key != null
+ V value = StrategyImpl.this.waitForValue(original);
+ success = true;
+ return value;
+ } finally {
+ if (!success) {
+ removeEntry();
+ }
+ }
+ }
+
+ /**
+ * Removes the entry in the event of an exception. Ideally,
+ * we'd clean up as soon as the computation completes, but we
+ * can't do that without keeping a reference to this entry from
+ * the original.
+ */
+ void removeEntry() {
+ internals.removeEntry(newEntry);
+ }
+ }
+
+ public ReferenceEntry<K, V> getNext(
+ ReferenceEntry<K, V> entry) {
+ return entry.getNext();
+ }
+
+ public void setInternals(
+ Internals<K, V, ReferenceEntry<K, V>> internals) {
+ this.internals = internals;
+ }
+
+ private static final long serialVersionUID = 0;
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException {
+ // Custom serialization code ensures that the key and value
+ // strengths are written before the map. We'll need them to
+ // deserialize the map entries.
+ out.writeObject(keyStrength);
+ out.writeObject(valueStrength);
+ out.writeLong(expirationNanos);
+
+ // TODO: It is possible for the strategy to try to use the map
+ // or internals during deserialization, for example, if an
+ // entry gets reclaimed. We could detect this case and queue up
+ // removals to be flushed after we deserialize the map.
+ out.writeObject(internals);
+ out.writeObject(map);
+ }
+
+ /**
+ * Fields used during deserialization. We use a nested class so we
+ * don't load them until we need them. We need to use reflection to
+ * set final fields outside of the constructor.
+ */
+ private static class Fields {
+ static final Field keyStrength = findField("keyStrength");
+ static final Field valueStrength = findField("valueStrength");
+ static final Field expirationNanos = findField("expirationNanos");
+ static final Field internals = findField("internals");
+ static final Field map = findField("map");
+
+ static Field findField(String name) {
+ try {
+ Field f = StrategyImpl.class.getDeclaredField(name);
+ f.setAccessible(true);
+ return f;
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ try {
+ Fields.keyStrength.set(this, in.readObject());
+ Fields.valueStrength.set(this, in.readObject());
+ Fields.expirationNanos.set(this, in.readLong());
+ Fields.internals.set(this, in.readObject());
+ Fields.map.set(this, in.readObject());
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ /** A reference to a value. */
+ private interface ValueReference<K, V> {
+ /**
+ * Gets the value. Does not block or throw exceptions.
+ */
+ V get();
+
+ /** Creates a copy of this reference for the given entry. */
+ ValueReference<K, V> copyFor(ReferenceEntry<K, V> entry);
+
+ /**
+ * Waits for a value that may still be computing. Unlike get(),
+ * this method can block (in the case of FutureValueReference) or
+ * throw an exception.
+ */
+ V waitForValue() throws InterruptedException;
+ }
+
+ private static final ValueReference<Object, Object> COMPUTING
+ = new ValueReference<Object, Object>() {
+ public Object get() {
+ return null;
+ }
+ public ValueReference<Object, Object> copyFor(
+ ReferenceEntry<Object, Object> entry) {
+ throw new AssertionError();
+ }
+ public Object waitForValue() {
+ throw new AssertionError();
+ }
+ };
+
+ /**
+ * Singleton placeholder that indicates a value is being computed.
+ */
+ @SuppressWarnings("unchecked")
+ // Safe because impl never uses a parameter or returns any non-null value
+ private static <K, V> ValueReference<K, V> computing() {
+ return (ValueReference<K, V>) COMPUTING;
+ }
+
+ /** Used to provide null output exceptions to other threads. */
+ private static class NullOutputExceptionReference<K, V>
+ implements ValueReference<K, V> {
+ final String message;
+ NullOutputExceptionReference(String message) {
+ this.message = message;
+ }
+ public V get() {
+ return null;
+ }
+ public ValueReference<K, V> copyFor(
+ ReferenceEntry<K, V> entry) {
+ return this;
+ }
+ public V waitForValue() {
+ throw new NullOutputException(message);
+ }
+ }
+
+ /** Used to provide computation exceptions to other threads. */
+ private static class ComputationExceptionReference<K, V>
+ implements ValueReference<K, V> {
+ final Throwable t;
+ ComputationExceptionReference(Throwable t) {
+ this.t = t;
+ }
+ public V get() {
+ return null;
+ }
+ public ValueReference<K, V> copyFor(
+ ReferenceEntry<K, V> entry) {
+ return this;
+ }
+ public V waitForValue() {
+ throw new AsynchronousComputationException(t);
+ }
+ }
+
+ /** Wrapper class ensures that queue isn't created until it's used. */
+ private static class QueueHolder {
+ static final FinalizableReferenceQueue queue
+ = new FinalizableReferenceQueue();
+ }
+
+ /**
+ * An entry in a reference map.
+ */
+ private interface ReferenceEntry<K, V> {
+ /**
+ * Gets the value reference from this entry.
+ */
+ ValueReference<K, V> getValueReference();
+
+ /**
+ * Sets the value reference for this entry.
+ *
+ * @param valueReference
+ */
+ void setValueReference(ValueReference<K, V> valueReference);
+
+ /**
+ * Removes this entry from the map if its value reference hasn't
+ * changed. Used to clean up after values. The value reference can
+ * just call this method on the entry so it doesn't have to keep
+ * its own reference to the map.
+ */
+ void valueReclaimed();
+
+ /** Gets the next entry in the chain. */
+ ReferenceEntry<K, V> getNext();
+
+ /** Gets the entry's hash. */
+ int getHash();
+
+ /** Gets the key for this entry. */
+ public K getKey();
+ }
+
+ /**
+ * Used for strongly-referenced keys.
+ */
+ private static class StrongEntry<K, V> implements ReferenceEntry<K, V> {
+ final K key;
+
+ StrongEntry(Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash) {
+ this.internals = internals;
+ this.key = key;
+ this.hash = hash;
+ }
+
+ public K getKey() {
+ return this.key;
+ }
+
+ // The code below is exactly the same for each entry type.
+
+ final Internals<K, V, ReferenceEntry<K, V>> internals;
+ final int hash;
+ volatile ValueReference<K, V> valueReference = computing();
+
+ public ValueReference<K, V> getValueReference() {
+ return valueReference;
+ }
+ public void setValueReference(
+ ValueReference<K, V> valueReference) {
+ this.valueReference = valueReference;
+ }
+ public void valueReclaimed() {
+ internals.removeEntry(this, null);
+ }
+ public ReferenceEntry<K, V> getNext() {
+ return null;
+ }
+ public int getHash() {
+ return hash;
+ }
+ }
+
+ private static class LinkedStrongEntry<K, V> extends StrongEntry<K, V> {
+
+ LinkedStrongEntry(Internals<K, V, ReferenceEntry<K, V>> internals,
+ K key, int hash, ReferenceEntry<K, V> next) {
+ super(internals, key, hash);
+ this.next = next;
+ }
+
+ final ReferenceEntry<K, V> next;
+
+ @Override public ReferenceEntry<K, V> getNext() {
+ return next;
+ }
+ }
+
+ /**
+ * Used for softly-referenced keys.
+ */
+ private static class SoftEntry<K, V> extends FinalizableSoftReference<K>
+ implements ReferenceEntry<K, V> {
+ SoftEntry(Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash) {
+ super(key, QueueHolder.queue);
+ this.internals = internals;
+ this.hash = hash;
+ }
+
+ public K getKey() {
+ return get();
+ }
+
+ public void finalizeReferent() {
+ internals.removeEntry(this);
+ }
+
+ // The code below is exactly the same for each entry type.
+
+ final Internals<K, V, ReferenceEntry<K, V>> internals;
+ final int hash;
+ volatile ValueReference<K, V> valueReference = computing();
+
+ public ValueReference<K, V> getValueReference() {
+ return valueReference;
+ }
+ public void setValueReference(
+ ValueReference<K, V> valueReference) {
+ this.valueReference = valueReference;
+ }
+ public void valueReclaimed() {
+ internals.removeEntry(this, null);
+ }
+ public ReferenceEntry<K, V> getNext() {
+ return null;
+ }
+ public int getHash() {
+ return hash;
+ }
+ }
+
+ private static class LinkedSoftEntry<K, V> extends SoftEntry<K, V> {
+ LinkedSoftEntry(Internals<K, V, ReferenceEntry<K, V>> internals,
+ K key, int hash, ReferenceEntry<K, V> next) {
+ super(internals, key, hash);
+ this.next = next;
+ }
+
+ final ReferenceEntry<K, V> next;
+
+ @Override public ReferenceEntry<K, V> getNext() {
+ return next;
+ }
+ }
+
+ /**
+ * Used for weakly-referenced keys.
+ */
+ private static class WeakEntry<K, V> extends FinalizableWeakReference<K>
+ implements ReferenceEntry<K, V> {
+ WeakEntry(Internals<K, V, ReferenceEntry<K, V>> internals, K key,
+ int hash) {
+ super(key, QueueHolder.queue);
+ this.internals = internals;
+ this.hash = hash;
+ }
+
+ public K getKey() {
+ return get();
+ }
+
+ public void finalizeReferent() {
+ internals.removeEntry(this);
+ }
+
+ // The code below is exactly the same for each entry type.
+
+ final Internals<K, V, ReferenceEntry<K, V>> internals;
+ final int hash;
+ volatile ValueReference<K, V> valueReference = computing();
+
+ public ValueReference<K, V> getValueReference() {
+ return valueReference;
+ }
+ public void setValueReference(
+ ValueReference<K, V> valueReference) {
+ this.valueReference = valueReference;
+ }
+ public void valueReclaimed() {
+ internals.removeEntry(this, null);
+ }
+ public ReferenceEntry<K, V> getNext() {
+ return null;
+ }
+ public int getHash() {
+ return hash;
+ }
+ }
+
+ private static class LinkedWeakEntry<K, V> extends WeakEntry<K, V> {
+ LinkedWeakEntry(Internals<K, V, ReferenceEntry<K, V>> internals,
+ K key, int hash, ReferenceEntry<K, V> next) {
+ super(internals, key, hash);
+ this.next = next;
+ }
+
+ final ReferenceEntry<K, V> next;
+
+ @Override public ReferenceEntry<K, V> getNext() {
+ return next;
+ }
+ }
+
+ /** References a weak value. */
+ private static class WeakValueReference<K, V>
+ extends FinalizableWeakReference<V>
+ implements ValueReference<K, V> {
+ final ReferenceEntry<K, V> entry;
+
+ WeakValueReference(V referent, ReferenceEntry<K, V> entry) {
+ super(referent, QueueHolder.queue);
+ this.entry = entry;
+ }
+
+ public void finalizeReferent() {
+ entry.valueReclaimed();
+ }
+
+ public ValueReference<K, V> copyFor(
+ ReferenceEntry<K, V> entry) {
+ return new WeakValueReference<K, V>(get(), entry);
+ }
+
+ public V waitForValue() throws InterruptedException {
+ return get();
+ }
+ }
+
+ /** References a soft value. */
+ private static class SoftValueReference<K, V>
+ extends FinalizableSoftReference<V>
+ implements ValueReference<K, V> {
+ final ReferenceEntry<K, V> entry;
+
+ SoftValueReference(V referent, ReferenceEntry<K, V> entry) {
+ super(referent, QueueHolder.queue);
+ this.entry = entry;
+ }
+
+ public void finalizeReferent() {
+ entry.valueReclaimed();
+ }
+
+ public ValueReference<K, V> copyFor(
+ ReferenceEntry<K, V> entry) {
+ return new SoftValueReference<K, V>(get(), entry);
+ }
+
+ public V waitForValue() throws InterruptedException {
+ return get();
+ }
+ }
+
+ /** References a strong value. */
+ private static class StrongValueReference<K, V>
+ implements ValueReference<K, V> {
+ final V referent;
+
+ StrongValueReference(V referent) {
+ this.referent = referent;
+ }
+
+ public V get() {
+ return referent;
+ }
+
+ public ValueReference<K, V> copyFor(
+ ReferenceEntry<K, V> entry) {
+ return this;
+ }
+
+ public V waitForValue() throws InterruptedException {
+ return get();
+ }
+ }
+}
diff --git a/src/com/google/inject/internal/NullOutputException.java b/src/com/google/inject/internal/NullOutputException.java
new file mode 100644
index 0000000..d673332
--- /dev/null
+++ b/src/com/google/inject/internal/NullOutputException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+/**
+ * Thrown when a computer function returns null. This subclass exists so
+ * that our ReferenceCache adapter can differentiate null output from null
+ * keys, but we don't want to make this public otherwise.
+ *
+ * @author Bob Lee
+ */
+class NullOutputException extends NullPointerException {
+ public NullOutputException(String s) {
+ super(s);
+ }
+}
diff --git a/src/com/google/inject/internal/Nullable.java b/src/com/google/inject/internal/Nullable.java
new file mode 100644
index 0000000..c38497e
--- /dev/null
+++ b/src/com/google/inject/internal/Nullable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The presence of this annotation on a method parameter indicates that
+ * {@code null} is an acceptable value for that parameter. It should not be
+ * used for parameters of primitive types.
+ *
+ * <p>This annotation may be used with the Google Web Toolkit (GWT).
+ *
+ * @author Kevin Bourrillion
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Nullable { }
diff --git a/src/com/google/inject/internal/ReferenceCache.java b/src/com/google/inject/internal/ReferenceCache.java
deleted file mode 100644
index ad8293c..0000000
--- a/src/com/google/inject/internal/ReferenceCache.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import static com.google.inject.internal.ReferenceType.STRONG;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Extends {@link ReferenceMap} to support lazy loading values by overriding
- * {@link #create(Object)}.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-public abstract class ReferenceCache<K, V>
- extends AbstractReferenceCache<K, V> {
-
- private static final long serialVersionUID = 0;
-
- public ReferenceCache(ReferenceType keyReferenceType,
- ReferenceType valueReferenceType) {
- super(keyReferenceType, valueReferenceType);
- }
-
- /**
- * Equivalent to {@code new ReferenceCache(STRONG, STRONG)}.
- */
- public ReferenceCache() {
- super(STRONG, STRONG);
- }
-
- /**
- * Override to lazy load values. Use as an alternative to {@link
- * #put(Object,Object)}. Invoked by getter if value isn't already cached.
- * Must not return {@code null}. This method will not be called again until
- * the garbage collector reclaims the returned value.
- */
- protected abstract V create(K key);
-
- V create(FutureValue<V> futureValue, K key) {
- return create(key);
- }
-
- /**
- * Returns a {@code ReferenceCache} delegating to the specified {@code
- * function}. The specified function must not return {@code null}.
- */
- public static <K, V> ReferenceCache<K, V> of(
- ReferenceType keyReferenceType,
- ReferenceType valueReferenceType,
- final Function<? super K, ? extends V> function) {
- checkNotNull(function, "function");
- return new ReferenceCache<K, V>(keyReferenceType, valueReferenceType) {
- protected V create(K key) {
- return function.apply(key);
- }
- private static final long serialVersionUID = 0;
- };
- }
-}
\ No newline at end of file
diff --git a/src/com/google/inject/internal/ReferenceMap.java b/src/com/google/inject/internal/ReferenceMap.java
deleted file mode 100644
index 4185a3b..0000000
--- a/src/com/google/inject/internal/ReferenceMap.java
+++ /dev/null
@@ -1,644 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import static com.google.inject.internal.ReferenceType.STRONG;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.lang.ref.Reference;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-/**
- * Concurrent hash map that wraps keys and/or values in soft or weak references.
- * Does not support null keys or values. Uses identity equality for weak and
- * soft keys.
- *
- * <p>The concurrent semantics of {@link ConcurrentHashMap} combined with the
- * fact that the garbage collector can asynchronously reclaim and clean up
- * keys and values at any time can lead to some racy semantics. For example,
- * {@link #size()} returns an upper bound on the size; that is, the actual size
- * may be smaller in cases where the key or value has been reclaimed but the map
- * entry has not been cleaned up yet.
- *
- * @author crazybob@google.com (Bob Lee)
- * @author fry@google.com (Charles Fry)
- */
-@SuppressWarnings("unchecked")
-public class ReferenceMap<K, V> extends AbstractMap<K, V>
- implements ConcurrentMap<K, V>, Serializable {
-
- transient ConcurrentMap<Object, Object> delegate;
-
- private final ReferenceType keyReferenceType;
- private final ReferenceType valueReferenceType;
-
- /**
- * Concurrent hash map that wraps keys and/or values based on specified
- * reference types.
- *
- * @param keyReferenceType key reference type
- * @param valueReferenceType value reference type
- */
- public ReferenceMap(
- ReferenceType keyReferenceType, ReferenceType valueReferenceType) {
- ensureNotNull(keyReferenceType, valueReferenceType);
-
- if (keyReferenceType == ReferenceType.PHANTOM
- || valueReferenceType == ReferenceType.PHANTOM) {
- throw new IllegalArgumentException("Phantom references not supported.");
- }
-
- this.delegate = new ConcurrentHashMap<Object, Object>();
- this.keyReferenceType = keyReferenceType;
- this.valueReferenceType = valueReferenceType;
- }
-
- V internalGet(K key) {
- Object valueReference = delegate.get(makeKeyReferenceAware(key));
- return dereferenceValue(valueReference);
- }
-
- public V get(final Object key) {
- checkNotNull(key, "key");
- return internalGet((K) key);
- }
-
- private V execute(Strategy strategy, K key, V value) {
- ensureNotNull(key, value);
- Object keyReference = referenceKey(key);
- return (V) strategy.execute(
- this, keyReference, referenceValue(keyReference, value)
- );
- }
-
- public V put(K key, V value) {
- return execute(putStrategy(), key, value);
- }
-
- public V remove(Object key) {
- checkNotNull(key, "key");
- Object referenceAwareKey = makeKeyReferenceAware(key);
- Object valueReference = delegate.remove(referenceAwareKey);
- return dereferenceValue(valueReference);
- }
-
- public int size() {
- return delegate.size();
- }
-
- public boolean isEmpty() {
- return delegate.isEmpty();
- }
-
- public boolean containsKey(Object key) {
- checkNotNull(key, "key");
- Object referenceAwareKey = makeKeyReferenceAware(key);
- return delegate.containsKey(referenceAwareKey);
- }
-
- public boolean containsValue(Object value) {
- checkNotNull(value, "value");
- for (Object valueReference : delegate.values()) {
- if (value.equals(dereferenceValue(valueReference))) {
- return true;
- }
- }
- return false;
- }
-
- public void putAll(Map<? extends K, ? extends V> t) {
- for (Map.Entry<? extends K, ? extends V> entry : t.entrySet()) {
- put(entry.getKey(), entry.getValue());
- }
- }
-
- public void clear() {
- delegate.clear();
- }
-
- public V putIfAbsent(K key, V value) {
- return execute(putIfAbsentStrategy(), key, value);
- }
-
- public boolean remove(Object key, Object value) {
- ensureNotNull(key, value);
- Object referenceAwareKey = makeKeyReferenceAware(key);
- Object referenceAwareValue = makeValueReferenceAware(value);
- return delegate.remove(referenceAwareKey, referenceAwareValue);
- }
-
- public boolean replace(K key, V oldValue, V newValue) {
- ensureNotNull(key, oldValue, newValue);
- Object keyReference = referenceKey(key);
-
- Object referenceAwareOldValue = makeValueReferenceAware(oldValue);
- return delegate.replace(keyReference, referenceAwareOldValue,
- referenceValue(keyReference, newValue)
- );
- }
-
- public V replace(K key, V value) {
- return execute(replaceStrategy(), key, value);
- }
-
- private transient volatile Set<Map.Entry<K, V>> entrySet = null;
-
- public Set<Map.Entry<K, V>> entrySet() {
- if (entrySet == null) {
- entrySet = new EntrySet();
- }
- return entrySet;
- }
-
- /** Dereferences an entry. Returns null if the key or value has been gc'ed. */
- private Entry dereferenceEntry(Map.Entry<Object, Object> entry) {
- K key = dereferenceKey(entry.getKey());
- V value = dereferenceValue(entry.getValue());
- return (key == null || value == null)
- ? null
- : new Entry(key, value);
- }
-
- /** Creates a reference for a key. */
- Object referenceKey(K key) {
- switch (keyReferenceType) {
- case STRONG:
- return key;
- case SOFT:
- return new SoftKeyReference(key);
- case WEAK:
- return new WeakKeyReference(key);
- default:
- throw new AssertionError();
- }
- }
-
- /** Converts a reference to a key. */
- private K dereferenceKey(Object o) {
- return (K) dereference(keyReferenceType, o);
- }
-
- /** Converts a reference to a value. */
- V dereferenceValue(Object o) {
- if (o == null) {
- return null;
- }
- Object value = dereference(valueReferenceType, o);
- if (o instanceof InternalReference) {
- InternalReference ref = (InternalReference) o;
- if (value == null) {
- // old value was garbage collected
- ref.finalizeReferent();
- }
- }
- return (V) value;
- }
-
- /** Returns the refererent for reference given its reference type. */
- private Object dereference(ReferenceType referenceType, Object reference) {
- return referenceType == STRONG ? reference : ((Reference) reference).get();
- }
-
- /** Creates a reference for a value. */
- Object referenceValue(Object keyReference, Object value) {
- switch (valueReferenceType) {
- case STRONG:
- return value;
- case SOFT:
- return new SoftValueReference(keyReference, value);
- case WEAK:
- return new WeakValueReference(keyReference, value);
- default:
- throw new AssertionError();
- }
- }
-
- /**
- * Wraps key so it can be compared to a referenced key for equality.
- */
- private Object makeKeyReferenceAware(Object o) {
- return keyReferenceType == STRONG ? o : new KeyReferenceAwareWrapper(o);
- }
-
- /** Wraps value so it can be compared to a referenced value for equality. */
- private Object makeValueReferenceAware(Object o) {
- return valueReferenceType == STRONG ? o : new ReferenceAwareWrapper(o);
- }
-
- /**
- * Marker interface to differentiate external and internal references. Also
- * duplicates FinalizableReference and Reference.get for internal use.
- */
- interface InternalReference {
- void finalizeReferent();
-
- Object get();
- }
-
- private static int keyHashCode(Object key) {
- return System.identityHashCode(key);
- }
-
- /*
- * Tests weak and soft references for identity equality. Compares references
- * to other references and wrappers. If o is a reference, this returns true if
- * r == o or if r and o reference the same non-null object. If o is a wrapper,
- * this returns true if r's referent is identical to the wrapped object.
- */
- private static boolean referenceEquals(Reference r, Object o) {
- // compare reference to reference.
- if (o instanceof InternalReference) {
- // are they the same reference? used in cleanup.
- if (o == r) {
- return true;
- }
-
- // do they reference identical values? used in conditional puts.
- Object referent = ((Reference) o).get();
- return referent != null && referent == r.get();
- }
-
- // is the wrapped object identical to the referent? used in lookups.
- return ((ReferenceAwareWrapper) o).unwrap() == r.get();
- }
-
- /**
- * Returns {@code true} if the specified value reference has been garbage
- * collected. The value behind the reference is also passed in, rather than
- * queried inside this method, to ensure that the return statement of this
- * method will still hold true after it has returned (that is, a value
- * reference exists outside of this method which will prevent that value from
- * being garbage collected).
- *
- * @param valueReference the value reference to be tested
- * @param value the object referenced by {@code valueReference}
- * @return {@code true} if {@code valueReference} is non-null and {@code
- * value} is null
- */
- private static boolean isExpired(Object valueReference, Object value) {
- return (valueReference != null) && (value == null);
- }
-
- /**
- * Big hack. Used to compare keys and values to referenced keys and values
- * without creating more references.
- */
- static class ReferenceAwareWrapper {
- final Object wrapped;
-
- ReferenceAwareWrapper(Object wrapped) {
- this.wrapped = wrapped;
- }
-
- Object unwrap() {
- return wrapped;
- }
-
- @Override public int hashCode() {
- return wrapped.hashCode();
- }
-
- @Override public boolean equals(Object obj) {
- // defer to reference's equals() logic.
- return obj.equals(this);
- }
- }
-
- /** Used for keys. Overrides hash code to use identity hash code. */
- static class KeyReferenceAwareWrapper extends ReferenceAwareWrapper {
- public KeyReferenceAwareWrapper(Object wrapped) {
- super(wrapped);
- }
-
- public int hashCode() {
- return System.identityHashCode(wrapped);
- }
- }
-
- class SoftKeyReference extends FinalizableSoftReference<Object>
- implements InternalReference {
- final int hashCode;
-
- public SoftKeyReference(Object key) {
- super(key);
- this.hashCode = keyHashCode(key);
- }
-
- public void finalizeReferent() {
- delegate.remove(this);
- }
-
- @Override public int hashCode() {
- return this.hashCode;
- }
-
- @Override public boolean equals(Object o) {
- return referenceEquals(this, o);
- }
- }
-
- class WeakKeyReference extends FinalizableWeakReference<Object>
- implements InternalReference {
- final int hashCode;
-
- public WeakKeyReference(Object key) {
- super(key);
- this.hashCode = keyHashCode(key);
- }
-
- public void finalizeReferent() {
- delegate.remove(this);
- }
-
- @Override public int hashCode() {
- return this.hashCode;
- }
-
- @Override public boolean equals(Object o) {
- return referenceEquals(this, o);
- }
- }
-
- class SoftValueReference extends FinalizableSoftReference<Object>
- implements InternalReference {
- final Object keyReference;
-
- public SoftValueReference(Object keyReference, Object value) {
- super(value);
- this.keyReference = keyReference;
- }
-
- public void finalizeReferent() {
- delegate.remove(keyReference, this);
- }
-
- @Override public boolean equals(Object obj) {
- return referenceEquals(this, obj);
- }
- }
-
- class WeakValueReference extends FinalizableWeakReference<Object>
- implements InternalReference {
- final Object keyReference;
-
- public WeakValueReference(Object keyReference, Object value) {
- super(value);
- this.keyReference = keyReference;
- }
-
- public void finalizeReferent() {
- delegate.remove(keyReference, this);
- }
-
- @Override public boolean equals(Object obj) {
- return referenceEquals(this, obj);
- }
- }
-
- protected interface Strategy {
- public Object execute(
- ReferenceMap map, Object keyReference, Object valueReference);
- }
-
- // TODO(crazybob): a getter called put() is probably a bad idea
- protected Strategy putStrategy() {
- return PutStrategy.PUT;
- }
-
- protected Strategy putIfAbsentStrategy() {
- return PutStrategy.PUT_IF_ABSENT;
- }
-
- protected Strategy replaceStrategy() {
- return PutStrategy.REPLACE;
- }
-
- private enum PutStrategy implements Strategy {
- PUT {
- public Object execute(
- ReferenceMap map, Object keyReference, Object valueReference) {
- return map.dereferenceValue(
- map.delegate.put(keyReference, valueReference));
- }
- },
-
- REPLACE {
- public Object execute(
- ReferenceMap map, Object keyReference, Object valueReference) {
- // ensure that the existing value is not collected
- do {
- Object existingValueReference;
- Object existingValue;
- do {
- existingValueReference = map.delegate.get(keyReference);
- existingValue = map.dereferenceValue(existingValueReference);
- } while (isExpired(existingValueReference, existingValue));
-
- if (existingValueReference == null) {
- // nothing to replace
- return false;
- }
-
- if (map.delegate.replace(
- keyReference, existingValueReference, valueReference)) {
- // existingValue didn't expire since we still have a reference to it
- return existingValue;
- }
- } while (true);
- }
- },
-
- PUT_IF_ABSENT {
- public Object execute(
- ReferenceMap map, Object keyReference, Object valueReference) {
- Object existingValueReference;
- Object existingValue;
- do {
- existingValueReference
- = map.delegate.putIfAbsent(keyReference, valueReference);
- existingValue = map.dereferenceValue(existingValueReference);
- } while (isExpired(existingValueReference, existingValue));
- return existingValue;
- }
- },
- }
-
- private static PutStrategy defaultPutStrategy;
-
- protected PutStrategy getPutStrategy() {
- return defaultPutStrategy;
- }
-
- class Entry implements Map.Entry<K, V> {
- final K key;
- V value;
-
- public Entry(K key, V value) {
- this.key = key;
- this.value = value;
- }
-
- public K getKey() {
- return this.key;
- }
-
- public V getValue() {
- return this.value;
- }
-
- public V setValue(V newValue) {
- value = newValue;
- return put(key, newValue);
- }
-
- public int hashCode() {
- return key.hashCode() * 31 + value.hashCode();
- }
-
- public boolean equals(Object o) {
- if (!(o instanceof ReferenceMap.Entry)) {
- return false;
- }
-
- Entry entry = (Entry) o;
- return key.equals(entry.key) && value.equals(entry.value);
- }
-
- public String toString() {
- return key + "=" + value;
- }
- }
-
- private class ReferenceIterator implements Iterator<Map.Entry<K, V>> {
- private Iterator<Map.Entry<Object, Object>> i =
- delegate.entrySet().iterator();
- private Map.Entry<K, V> nextEntry;
- private Map.Entry<K, V> lastReturned;
-
- public ReferenceIterator() {
- advance();
- }
-
- private void advance() {
- while (i.hasNext()) {
- Map.Entry<K, V> entry = dereferenceEntry(i.next());
- if (entry != null) {
- nextEntry = entry;
- return;
- }
- }
-
- // nothing left
- nextEntry = null;
- }
-
- public boolean hasNext() {
- return nextEntry != null;
- }
-
- public Map.Entry<K, V> next() {
- if (nextEntry == null) {
- throw new NoSuchElementException();
- }
- lastReturned = nextEntry;
- advance();
- return lastReturned;
- }
-
- public void remove() {
- ReferenceMap.this.remove(lastReturned.getKey());
- }
- }
-
- private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
- public Iterator<Map.Entry<K, V>> iterator() {
- return new ReferenceIterator();
- }
-
- public int size() {
- return delegate.size();
- }
-
- public boolean contains(Object o) {
- if (!(o instanceof Map.Entry)) {
- return false;
- }
- Map.Entry<K, V> e = (Map.Entry<K, V>) o;
- V v = ReferenceMap.this.get(e.getKey());
- return v != null && v.equals(e.getValue());
- }
-
- public boolean remove(Object o) {
- if (!(o instanceof Map.Entry)) {
- return false;
- }
- Map.Entry<K, V> e = (Map.Entry<K, V>) o;
- return ReferenceMap.this.remove(e.getKey(), e.getValue());
- }
-
- public void clear() {
- delegate.clear();
- }
- }
-
- static void ensureNotNull(Object... array) {
- for (int i = 0; i < array.length; i++) {
- if (array[i] == null) {
- throw new NullPointerException("Argument #" + i + " is null.");
- }
- }
- }
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.defaultWriteObject();
- out.writeInt(size());
- for (Map.Entry<Object, Object> entry : delegate.entrySet()) {
- Object key = dereferenceKey(entry.getKey());
- Object value = dereferenceValue(entry.getValue());
-
- // don't persist gc'ed entries.
- if (key != null && value != null) {
- out.writeObject(key);
- out.writeObject(value);
- }
- }
- out.writeObject(null);
- }
-
- private void readObject(ObjectInputStream in)
- throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- int size = in.readInt();
- this.delegate = new ConcurrentHashMap<Object, Object>(size);
- while (true) {
- K key = (K) in.readObject();
- if (key == null) {
- break;
- }
- V value = (V) in.readObject();
- put(key, value);
- }
- }
-
- private static final long serialVersionUID = 0;
-}
diff --git a/src/com/google/inject/internal/ReferenceType.java b/src/com/google/inject/internal/ReferenceType.java
deleted file mode 100644
index 671dbc0..0000000
--- a/src/com/google/inject/internal/ReferenceType.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-/**
- * Reference type. Used to specify what type of reference to keep to a
- * referent.
- *
- * @see java.lang.ref.Reference
- * @author crazybob@google.com (Bob Lee)
- */
-public enum ReferenceType {
-
- /**
- * Prevents referent from being reclaimed by the garbage collector.
- */
- STRONG,
-
- /**
- * Referent reclaimed in an LRU fashion when the VM runs low on memory and
- * no strong references exist.
- *
- * @see java.lang.ref.SoftReference
- */
- SOFT,
-
- /**
- * Referent reclaimed when no strong or soft references exist.
- *
- * @see java.lang.ref.WeakReference
- */
- WEAK,
-
- /**
- * Similar to weak references except the garbage collector doesn't actually
- * reclaim the referent. More flexible alternative to finalization.
- *
- * @see java.lang.ref.PhantomReference
- */
- PHANTOM
-}
diff --git a/src/com/google/inject/internal/StackTraceElements.java b/src/com/google/inject/internal/StackTraceElements.java
index 99c7952..f90db4d 100644
--- a/src/com/google/inject/internal/StackTraceElements.java
+++ b/src/com/google/inject/internal/StackTraceElements.java
@@ -16,8 +16,6 @@
package com.google.inject.internal;
-import static com.google.inject.internal.ReferenceType.SOFT;
-import static com.google.inject.internal.ReferenceType.WEAK;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
@@ -30,17 +28,17 @@
*/
public class StackTraceElements {
- static final Map<Class<?>, LineNumbers> lineNumbersCache
- = new ReferenceCache<Class<?>, LineNumbers>(WEAK, SOFT) {
- protected LineNumbers create(Class<?> key) {
- try {
- return new LineNumbers(key);
- }
- catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- };
+ static final Map<Class<?>, LineNumbers> lineNumbersCache = new MapMaker().weakKeys().softValues()
+ .makeComputingMap(new Function<Class<?>, LineNumbers>() {
+ public LineNumbers apply(Class<?> key) {
+ try {
+ return new LineNumbers(key);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
public static Object forMember(Member member) {
if (member == null) {
diff --git a/struts2/example/struts2-example.iml b/struts2/example/struts2-example.iml
index 3d24291..fd5fc7d 100644
--- a/struts2/example/struts2-example.iml
+++ b/struts2/example/struts2-example.iml
@@ -154,6 +154,7 @@
<SOURCES />
</library>
</orderEntry>
+ <orderEntryProperties />
</component>
</module>
diff --git a/struts2/plugin/struts2-plugin.iml b/struts2/plugin/struts2-plugin.iml
index 2733e1c..5eb5f69 100644
--- a/struts2/plugin/struts2-plugin.iml
+++ b/struts2/plugin/struts2-plugin.iml
@@ -135,6 +135,7 @@
</library>
</orderEntry>
<orderEntry type="module" module-name="servlet" />
+ <orderEntryProperties />
</component>
</module>
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index f7db268..f3f2f61 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -18,10 +18,9 @@
import com.google.inject.internal.FinalizableReferenceQueueTest;
import com.google.inject.internal.LineNumbersTest;
-import com.google.inject.internal.ReferenceCacheTest;
-import com.google.inject.internal.ReferenceMapTest;
-import com.google.inject.internal.ReferenceMapTestSuite;
+import com.google.inject.internal.MapMakerTestSuite;
import com.google.inject.internal.UniqueAnnotationsTest;
+import com.google.inject.internal.Jsr166HashMapTest;
import com.google.inject.matcher.MatcherTest;
import com.google.inject.name.NamesTest;
import com.google.inject.spi.ElementsTest;
@@ -74,7 +73,6 @@
suite.addTestSuite(ProviderInjectionTest.class);
suite.addTestSuite(ProvisionExceptionTest.class);
suite.addTestSuite(ProxyFactoryTest.class);
- suite.addTest(ReferenceMapTestSuite.suite());
suite.addTestSuite(ReflectionTest.class);
suite.addTestSuite(ScopesTest.class);
suite.addTestSuite(SerializationTest.class);
@@ -95,8 +93,8 @@
// internal
suite.addTestSuite(FinalizableReferenceQueueTest.class);
suite.addTestSuite(LineNumbersTest.class);
- suite.addTestSuite(ReferenceCacheTest.class);
- suite.addTestSuite(ReferenceMapTest.class);
+ suite.addTest(MapMakerTestSuite.suite());
+ suite.addTestSuite(Jsr166HashMapTest.class);
suite.addTestSuite(TypesTest.class);
suite.addTestSuite(UniqueAnnotationsTest.class);
suite.addTestSuite(BytecodeGenTest.class);
diff --git a/test/com/google/inject/internal/FinalizableReferenceQueueTest.java b/test/com/google/inject/internal/FinalizableReferenceQueueTest.java
index 2648669..eb66ea6 100644
--- a/test/com/google/inject/internal/FinalizableReferenceQueueTest.java
+++ b/test/com/google/inject/internal/FinalizableReferenceQueueTest.java
@@ -1,21 +1,9 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
package com.google.inject.internal;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.net.URLClassLoader;
import junit.framework.TestCase;
/**
@@ -23,15 +11,23 @@
*/
public class FinalizableReferenceQueueTest extends TestCase {
+ private FinalizableReferenceQueue frq;
+
+ @Override
+ protected void tearDown() throws Exception {
+ frq = null;
+ }
+
public void testFinalizeReferentCalled() {
- MockReference reference = new MockReference();
- reference.enqueue();
+ MockReference reference = new MockReference(
+ frq = new FinalizableReferenceQueue());
// wait up to 5s
- for (int i = 0; i < 50; i++) {
+ for (int i = 0; i < 500; i++) {
if (reference.finalizeReferentCalled) {
return;
}
try {
+ System.gc();
Thread.sleep(10);
} catch (InterruptedException e) { /* ignore */ }
}
@@ -40,14 +36,109 @@
static class MockReference extends FinalizableWeakReference<Object> {
- boolean finalizeReferentCalled;
+ volatile boolean finalizeReferentCalled;
- MockReference() {
- super(new Object());
+ MockReference(FinalizableReferenceQueue frq) {
+ super(new Object(), frq);
}
public void finalizeReferent() {
finalizeReferentCalled = true;
}
}
+
+ /**
+ * Keeps a weak reference to the underlying reference queue. When this
+ * reference is cleared, we know that the background thread has stopped
+ * and released its strong reference.
+ */
+ private WeakReference<ReferenceQueue<Object>> queueReference;
+
+ public void testThatFinalizerStops() {
+ weaklyReferenceQueue();
+
+ // wait up to 5s
+ for (int i = 0; i < 500; i++) {
+ if (queueReference.get() == null) {
+ return;
+ }
+ try {
+ System.gc();
+ Thread.sleep(10);
+ } catch (InterruptedException e) { /* ignore */ }
+ }
+ fail();
+ }
+
+ /**
+ * If we don't keep a strong reference to the reference object, it won't
+ * be enqueued.
+ */
+ FinalizableWeakReference<Object> reference;
+
+ /**
+ * Create the FRQ in a method that goes out of scope so that we're sure
+ * it will be reclaimed.
+ */
+ private void weaklyReferenceQueue() {
+ frq = new FinalizableReferenceQueue();
+ queueReference = new WeakReference<ReferenceQueue<Object>>(frq.queue);
+
+ /*
+ * Queue and clear a reference for good measure. We test later on that
+ * the finalizer thread stopped, but we should test that it actually
+ * started first.
+ */
+ reference = new FinalizableWeakReference<Object>(new Object(), frq) {
+ public void finalizeReferent() {
+ reference = null;
+ frq = null;
+ }
+ };
+ }
+
+ public void testDecoupledLoader() {
+ FinalizableReferenceQueue.DecoupledLoader decoupledLoader =
+ new FinalizableReferenceQueue.DecoupledLoader() {
+ @Override
+ URLClassLoader newLoader(URL base) {
+ return new DecoupledClassLoader(new URL[] { base });
+ }
+ };
+
+ Class<?> finalizerCopy = decoupledLoader.loadFinalizer();
+
+ assertNotNull(finalizerCopy);
+ assertNotSame(Finalizer.class, finalizerCopy);
+
+ assertNotNull(FinalizableReferenceQueue.getStartFinalizer(finalizerCopy));
+ }
+
+ static class DecoupledClassLoader extends URLClassLoader {
+
+ public DecoupledClassLoader(URL[] urls) {
+ super(urls);
+ }
+
+ @Override
+ protected synchronized Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+ // Force Finalizer to load from this class loader, not its parent.
+ if (name.equals(Finalizer.class.getName())) {
+ Class<?> clazz = findClass(name);
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ public void testGetFinalizerUrl() {
+ assertNotNull(getClass().getResource("Finalizer.class"));
+ }
}
+
+
diff --git a/test/com/google/inject/internal/ForwardingCollection.java b/test/com/google/inject/internal/ForwardingCollection.java
new file mode 100644
index 0000000..56d7d2f
--- /dev/null
+++ b/test/com/google/inject/internal/ForwardingCollection.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import static com.google.inject.internal.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * A collection which forwards all its method calls to another collection.
+ * Subclasses should override one or more methods to modify the behavior of
+ * the backing collection as desired per the <a
+ * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
+ *
+ * @see ForwardingObject
+ * @author Kevin Bourrillion
+ */
+public abstract class ForwardingCollection<E> extends ForwardingObject
+ implements Collection<E> {
+
+ @Override protected abstract Collection<E> delegate();
+
+ public Iterator<E> iterator() {
+ return delegate().iterator();
+ }
+
+ public int size() {
+ return delegate().size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This method always throws a {@link NullPointerException} when
+ * {@code collection} is null.
+ */
+ public boolean removeAll(Collection<?> collection) {
+ return delegate().removeAll(checkNotNull(collection));
+ }
+
+ public boolean isEmpty() {
+ return delegate().isEmpty();
+ }
+
+ public boolean contains(Object object) {
+ return delegate().contains(object);
+ }
+
+ public Object[] toArray() {
+ return delegate().toArray();
+ }
+
+ public <T> T[] toArray(T[] array) {
+ return delegate().toArray(array);
+ }
+
+ public boolean add(E element) {
+ return delegate().add(element);
+ }
+
+ public boolean remove(Object object) {
+ return delegate().remove(object);
+ }
+
+ public boolean containsAll(Collection<?> collection) {
+ return delegate().containsAll(collection);
+ }
+
+ public boolean addAll(Collection<? extends E> collection) {
+ return delegate().addAll(collection);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This method always throws a {@link NullPointerException} when
+ * {@code collection} is null.
+ */
+ public boolean retainAll(Collection<?> collection) {
+ return delegate().retainAll(checkNotNull(collection));
+ }
+
+ public void clear() {
+ delegate().clear();
+ }
+}
diff --git a/test/com/google/inject/internal/ForwardingConcurrentMap.java b/test/com/google/inject/internal/ForwardingConcurrentMap.java
new file mode 100644
index 0000000..91db495
--- /dev/null
+++ b/test/com/google/inject/internal/ForwardingConcurrentMap.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A concurrent map which forwards all its method calls to another concurrent
+ * map. Subclasses should override one or more methods to modify the behavior of
+ * the backing map as desired per the <a
+ * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
+ *
+ * @see ForwardingObject
+ * @author Charles Fry
+ */
+public abstract class ForwardingConcurrentMap<K, V> extends ForwardingMap<K, V>
+ implements ConcurrentMap<K, V> {
+
+ @Override protected abstract ConcurrentMap<K, V> delegate();
+
+ public V putIfAbsent(K key, V value) {
+ return delegate().putIfAbsent(key, value);
+ }
+
+ public boolean remove(Object key, Object value) {
+ return delegate().remove(key, value);
+ }
+
+ public V replace(K key, V value) {
+ return delegate().replace(key, value);
+ }
+
+ public boolean replace(K key, V oldValue, V newValue) {
+ return delegate().replace(key, oldValue, newValue);
+ }
+
+}
diff --git a/test/com/google/inject/internal/ForwardingMap.java b/test/com/google/inject/internal/ForwardingMap.java
new file mode 100644
index 0000000..c73d634
--- /dev/null
+++ b/test/com/google/inject/internal/ForwardingMap.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import com.google.inject.internal.Nullable;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A map which forwards all its method calls to another map. Subclasses should
+ * override one or more methods to modify the behavior of the backing map as
+ * desired per the <a
+ * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
+ *
+ * @see ForwardingObject
+ * @author Kevin Bourrillion
+ * @author Jared Levy
+ */
+public abstract class ForwardingMap<K, V> extends ForwardingObject
+ implements Map<K, V> {
+
+ @Override protected abstract Map<K, V> delegate();
+
+ public int size() {
+ return delegate().size();
+ }
+
+ public boolean isEmpty() {
+ return delegate().isEmpty();
+ }
+
+ public V remove(Object object) {
+ return delegate().remove(object);
+ }
+
+ public void clear() {
+ delegate().clear();
+ }
+
+ public boolean containsKey(Object key) {
+ return delegate().containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return delegate().containsValue(value);
+ }
+
+ public V get(Object key) {
+ return delegate().get(key);
+ }
+
+ public V put(K key, V value) {
+ return delegate().put(key, value);
+ }
+
+ public void putAll(Map<? extends K, ? extends V> map) {
+ delegate().putAll(map);
+ }
+
+ private transient Set<K> keySet;
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The returned set's {@code removeAll} and {@code retainAll} methods
+ * always throw a {@link NullPointerException} when given a null collection.
+ */
+ public Set<K> keySet() {
+ return (keySet == null) ? keySet = createKeySet() : keySet;
+ }
+
+ /**
+ * Generates a {@link Set} for use by {@link #keySet()}.
+ *
+ * <p>ForwardingMap's implementation of keySet() calls this method to
+ * generate a collection of values, and then reuses that Set
+ * for subsequent invocations. By default, this Set is essentially the
+ * result of invoking keySet() on the delegate. Override this method if you
+ * want to provide another implementation.
+ *
+ * @return A set for use by keySet().
+ */
+ protected Set<K> createKeySet() {
+ final Set<K> delegate = delegate().keySet();
+ return new ForwardingSet<K>() {
+ @Override protected Set<K> delegate() {
+ return delegate;
+ }
+ };
+ }
+
+ private transient Collection<V> values;
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The returned collection's {@code removeAll} and {@code retainAll}
+ * methods always throw a {@link NullPointerException} when given a null
+ * collection.
+ */
+ public Collection<V> values() {
+ return (values == null) ? values = createValues() : values;
+ }
+
+ /**
+ * Generates a {@link Collection} for use by {@link #values()}.
+ *
+ * <p>ForwardingMap's implementation of {@code values()} calls this method to
+ * generate a collection of values, and then reuses that collection
+ * for subsequent invocations. By default, this collection is essentially the
+ * result of invoking values() on the delegate. Override this method if you
+ * want to provide another implementation.
+ *
+ * @return A set for use by values().
+ */
+ protected Collection<V> createValues() {
+ final Collection<V> delegate = delegate().values();
+ return new ForwardingCollection<V>() {
+ @Override protected Collection<V> delegate() {
+ return delegate;
+ }
+ };
+ }
+
+ private transient Set<Entry<K, V>> entrySet;
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The returned set's {@code removeAll} and {@code retainAll} methods
+ * always throw a {@link NullPointerException} when given a null collection.
+ */
+ public Set<Entry<K, V>> entrySet() {
+ return (entrySet == null) ? entrySet = createEntrySet() : entrySet;
+ }
+
+ /**
+ * Generates a {@link Set} for use by {@link #entrySet()}.
+ *
+ * <p>ForwardingMap's implementation of entrySet() calls this method to
+ * generate a set of entries, and then reuses that set for subsequent
+ * invocations. By default, this set is essentially the result of invoking
+ * entrySet() on the delegate. Override this method if you want to
+ * provide another implementation.
+ *
+ * @return A set for use by entrySet().
+ */
+ protected Set<Entry<K, V>> createEntrySet() {
+ final Set<Entry<K, V>> delegate = delegate().entrySet();
+ return new ForwardingSet<Entry<K, V>>() {
+ @Override protected Set<Entry<K, V>> delegate() {
+ return delegate;
+ }
+ };
+ }
+
+ @Override public boolean equals(@Nullable Object object) {
+ return object == this || delegate().equals(object);
+ }
+
+ @Override public int hashCode() {
+ return delegate().hashCode();
+ }
+}
diff --git a/test/com/google/inject/internal/ForwardingObject.java b/test/com/google/inject/internal/ForwardingObject.java
new file mode 100644
index 0000000..f41898a
--- /dev/null
+++ b/test/com/google/inject/internal/ForwardingObject.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.io.Serializable;
+
+/**
+ * An abstract base class for implementing the <a
+ * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
+ * The {@link #delegate()} method must be overridden to return the instance
+ * being decorated.
+ *
+ * This class does <i>not</i> forward the {@code hashCode} and {@code equals}
+ * methods through to the backing object, but relies on {@code Object}'s
+ * implementation. This is necessary to preserve the symmetry of {@code equals}.
+ * Custom definitions of equality are usually based on an interface, such as
+ * {@code Set} or {@code List}, so that the implementation of {@code equals} can
+ * cast the object being tested for equality to the custom interface. {@code
+ * ForwardingObject} implements no such custom interfaces directly; they
+ * are implemented only in subclasses. Therefore, forwarding {@code equals}
+ * would break symmetry, as the forwarding object might consider itself equal to
+ * the object being tested, but the reverse could not be true. This behavior is
+ * consistent with the JDK's collection wrappers, such as
+ * {@link java.util.Collections#unmodifiableCollection}. Use an
+ * interface-specific subclass of {@code ForwardingObject}, such as {@link
+ * ForwardingList}, to preserve equality behavior, or override {@code equals}
+ * directly.
+ *
+ * <p>The {@code toString} method is forwarded to the delegate. Although this
+ * class does not implement {@link Serializable}, a serializable subclass may be
+ * created since this class has a parameter-less constructor.
+ *
+ * @author Mike Bostock
+ */
+public abstract class ForwardingObject {
+
+ /** Sole constructor. */
+ protected ForwardingObject() {}
+
+ /**
+ * Returns the backing delegate instance that methods are forwarded to.
+ * Abstract subclasses generally override the {@link ForwardingObject} method
+ * with an abstract method that has a more specific return type, such as
+ * {@link ForwardingSet#delegate}. Concrete subclasses override this method to
+ * supply the instance being decorated.
+ */
+ protected abstract Object delegate();
+
+ /**
+ * Returns the string representation generated by the delegate's
+ * {@code toString} method.
+ */
+ @Override public String toString() {
+ return delegate().toString();
+ }
+
+ /* No equals or hashCode. See class comments for details. */
+}
diff --git a/test/com/google/inject/internal/ForwardingSet.java b/test/com/google/inject/internal/ForwardingSet.java
new file mode 100644
index 0000000..0e352c3
--- /dev/null
+++ b/test/com/google/inject/internal/ForwardingSet.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import com.google.inject.internal.Nullable;
+
+import java.util.Set;
+
+/**
+ * A set which forwards all its method calls to another set. Subclasses should
+ * override one or more methods to modify the behavior of the backing set as
+ * desired per the <a
+ * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
+ *
+ * @see ForwardingObject
+ * @author Kevin Bourrillion
+ */
+public abstract class ForwardingSet<E> extends ForwardingCollection<E>
+ implements Set<E> {
+
+ @Override protected abstract Set<E> delegate();
+
+ @Override public boolean equals(@Nullable Object object) {
+ return object == this || delegate().equals(object);
+ }
+
+ @Override public int hashCode() {
+ return delegate().hashCode();
+ }
+}
diff --git a/test/com/google/inject/internal/Jsr166HashMap.java b/test/com/google/inject/internal/Jsr166HashMap.java
new file mode 100644
index 0000000..a176720
--- /dev/null
+++ b/test/com/google/inject/internal/Jsr166HashMap.java
@@ -0,0 +1,172 @@
+/*
+ * Written by Doug Lea with assistance from members of JCP JSR-166
+ * Expert Group and released to the public domain, as explained at
+ * http://creativecommons.org/licenses/publicdomain
+ * Other contributors include Andrew Wright, Jeffrey Hayes,
+ * Pat Fisher, Mike Judd.
+ */
+
+package com.google.inject.internal;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A copy of {@link java.util.concurrent.ConcurrentHashMap} used to test {@link
+ * com.google.inject.internal.CustomConcurrentHashMap}. This also serves
+ * as the examples in the CustomConcurrentHashMap Javadocs.
+ */
+public class Jsr166HashMap<K, V> extends ForwardingConcurrentMap<K, V>
+ implements Serializable {
+
+ static class ConcurrentHashMapStrategy<K, V>
+ implements CustomConcurrentHashMap.Strategy<K, V,
+ InternalEntry<K, V>>, Serializable {
+ public InternalEntry<K, V> newEntry(K key, int hash,
+ InternalEntry<K, V> next) {
+ return new InternalEntry<K,V>(key, hash, null, next);
+ }
+ public InternalEntry<K, V> copyEntry(K key,
+ InternalEntry<K, V> original, InternalEntry<K, V> next) {
+ return new InternalEntry<K, V>(key, original.hash, original.value, next);
+ }
+ public void setValue(InternalEntry<K, V> entry, V value) {
+ entry.value = value;
+ }
+ public V getValue(InternalEntry<K, V> entry) { return entry.value; }
+ public boolean equalKeys(K a, Object b) { return a.equals(b); }
+ public boolean equalValues(V a, Object b) { return a.equals(b); }
+ public int hashKey(Object key) { return key.hashCode(); }
+ public K getKey(InternalEntry<K, V> entry) { return entry.key; }
+ public InternalEntry<K, V> getNext(InternalEntry<K, V> entry) {
+ return entry.next;
+ }
+ public int getHash(InternalEntry<K, V> entry) { return entry.hash; }
+ public void setInternals(CustomConcurrentHashMap.Internals<K, V,
+ InternalEntry<K, V>> internals) {} // ignored
+ }
+
+ static class InternalEntry<K, V> {
+ final K key;
+ final int hash;
+ final InternalEntry<K, V> next;
+ volatile V value;
+ InternalEntry(K key, int hash, V value, InternalEntry<K, V> next) {
+ this.key = key;
+ this.hash = hash;
+ this.value = value;
+ this.next = next;
+ }
+ }
+
+ /* ---------------- Public operations -------------- */
+
+ /**
+ * The default initial capacity for this table,
+ * used when not otherwise specified in a constructor.
+ */
+ static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+ /**
+ * The default load factor for this table, used when not
+ * otherwise specified in a constructor.
+ */
+ static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ /**
+ * The default concurrency level for this table, used when not
+ * otherwise specified in a constructor.
+ */
+ static final int DEFAULT_CONCURRENCY_LEVEL = 16;
+
+ final ConcurrentMap<K, V> delegate;
+
+ protected ConcurrentMap<K, V> delegate() {
+ return delegate;
+ }
+
+ /**
+ * Creates a new, empty map with the specified initial capacity, load factor
+ * and concurrency level.
+ *
+ * @param initialCapacity the initial capacity. The implementation performs
+ * internal sizing to accommodate this many
+ * elements.
+ * @param loadFactor the load factor threshold, used to control
+ * resizing. Resizing may be performed when the
+ * average number of elements per bin exceeds this
+ * threshold.
+ * @param concurrencyLevel the estimated number of concurrently updating
+ * threads. The implementation performs internal
+ * sizing to try to accommodate this many threads.
+ * @throws IllegalArgumentException if the initial capacity is negative or
+ * the load factor or concurrencyLevel are
+ * nonpositive.
+ */
+ public Jsr166HashMap(int initialCapacity,
+ float loadFactor, int concurrencyLevel) {
+ this.delegate = new CustomConcurrentHashMap.Builder()
+ .initialCapacity(initialCapacity)
+ .loadFactor(loadFactor)
+ .concurrencyLevel(concurrencyLevel)
+ .buildMap(new ConcurrentHashMapStrategy<K, V>());
+ }
+
+ /**
+ * Creates a new, empty map with the specified initial capacity and load
+ * factor and with the default concurrencyLevel (16).
+ *
+ * @param initialCapacity The implementation performs internal sizing to
+ * accommodate this many elements.
+ * @param loadFactor the load factor threshold, used to control
+ * resizing. Resizing may be performed when the
+ * average number of elements per bin exceeds this
+ * threshold.
+ * @throws IllegalArgumentException if the initial capacity of elements is
+ * negative or the load factor is
+ * nonpositive
+ * @since 1.6
+ */
+ public Jsr166HashMap(int initialCapacity, float loadFactor) {
+ this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
+ }
+
+ /**
+ * Creates a new, empty map with the specified initial capacity, and with
+ * default load factor (0.75) and concurrencyLevel (16).
+ *
+ * @param initialCapacity the initial capacity. The implementation performs
+ * internal sizing to accommodate this many
+ * elements.
+ * @throws IllegalArgumentException if the initial capacity of elements is
+ * negative.
+ */
+ public Jsr166HashMap(int initialCapacity) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
+ }
+
+ /**
+ * Creates a new, empty map with a default initial capacity (16), load
+ * factor (0.75) and concurrencyLevel (16).
+ */
+ public Jsr166HashMap() {
+ this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR,
+ DEFAULT_CONCURRENCY_LEVEL);
+ }
+
+ /**
+ * Creates a new map with the same mappings as the given map. The map is
+ * created with a capacity of 1.5 times the number of mappings in the given
+ * map or 16 (whichever is greater), and a default load factor (0.75) and
+ * concurrencyLevel (16).
+ *
+ * @param m the map
+ */
+ public Jsr166HashMap(Map<? extends K, ? extends V> m) {
+ this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
+ DEFAULT_INITIAL_CAPACITY),
+ DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
+ putAll(m);
+ }
+}
\ No newline at end of file
diff --git a/test/com/google/inject/internal/Jsr166HashMapTest.java b/test/com/google/inject/internal/Jsr166HashMapTest.java
new file mode 100644
index 0000000..a337fdd
--- /dev/null
+++ b/test/com/google/inject/internal/Jsr166HashMapTest.java
@@ -0,0 +1,627 @@
+/*
+ * Written by Doug Lea with assistance from members of JCP JSR-166
+ * Expert Group and released to the public domain, as explained at
+ * http://creativecommons.org/licenses/publicdomain
+ * Other contributors include Andrew Wright, Jeffrey Hayes,
+ * Pat Fisher, Mike Judd.
+ */
+
+package com.google.inject.internal;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ConcurrentHashMap tests copied from ConcurrentHashMapTest. Useful as a
+ * test case for CustomConcurrentHashMap.
+ */
+public class Jsr166HashMapTest extends TestCase {
+
+ /*
+ * The following two methods and constants were copied from JSR166TestCase.
+ */
+
+ /**
+ * fail with message "should throw exception"
+ */
+ public void shouldThrow() {
+ fail("Should throw exception");
+ }
+
+ /**
+ * fail with message "Unexpected exception"
+ */
+ public void unexpectedException() {
+ fail("Unexpected exception");
+ }
+
+ static final Integer zero = new Integer(0);
+ static final Integer one = new Integer(1);
+ static final Integer two = new Integer(2);
+ static final Integer three = new Integer(3);
+ static final Integer four = new Integer(4);
+ static final Integer five = new Integer(5);
+ static final Integer six = new Integer(6);
+ static final Integer seven = new Integer(7);
+ static final Integer eight = new Integer(8);
+ static final Integer nine = new Integer(9);
+ static final Integer m1 = new Integer(-1);
+ static final Integer m2 = new Integer(-2);
+ static final Integer m3 = new Integer(-3);
+ static final Integer m4 = new Integer(-4);
+ static final Integer m5 = new Integer(-5);
+ static final Integer m6 = new Integer(-6);
+ static final Integer m10 = new Integer(-10);
+
+ /**
+ * Create a map from Integers 1-5 to Strings "A"-"E".
+ */
+ private static Jsr166HashMap map5() {
+ Jsr166HashMap map = new Jsr166HashMap(5);
+ assertTrue(map.isEmpty());
+ map.put(one, "A");
+ map.put(two, "B");
+ map.put(three, "C");
+ map.put(four, "D");
+ map.put(five, "E");
+ assertFalse(map.isEmpty());
+ assertEquals(5, map.size());
+ return map;
+ }
+
+ /**
+ * clear removes all pairs
+ */
+ public void testClear() {
+ Jsr166HashMap map = map5();
+ map.clear();
+ assertEquals(map.size(), 0);
+ }
+
+ /**
+ * Maps with same contents are equal
+ */
+ public void testEquals() {
+ Jsr166HashMap map1 = map5();
+ Jsr166HashMap map2 = map5();
+ assertEquals(map1, map2);
+ assertEquals(map2, map1);
+ map1.clear();
+ assertFalse(map1.equals(map2));
+ assertFalse(map2.equals(map1));
+ }
+
+ /**
+ * containsKey returns true for contained key
+ */
+ public void testContainsKey() {
+ Jsr166HashMap map = map5();
+ assertTrue(map.containsKey(one));
+ assertFalse(map.containsKey(zero));
+ }
+
+ /**
+ * containsValue returns true for held values
+ */
+ public void testContainsValue() {
+ Jsr166HashMap map = map5();
+ assertTrue(map.containsValue("A"));
+ assertFalse(map.containsValue("Z"));
+ }
+
+ /**
+ * get returns the correct element at the given key, or null if not present
+ */
+ public void testGet() {
+ Jsr166HashMap map = map5();
+ assertEquals("A", (String) map.get(one));
+ Jsr166HashMap empty = new Jsr166HashMap();
+ assertNull(map.get("anything"));
+ }
+
+ /**
+ * isEmpty is true of empty map and false for non-empty
+ */
+ public void testIsEmpty() {
+ Jsr166HashMap empty = new Jsr166HashMap();
+ Jsr166HashMap map = map5();
+ assertTrue(empty.isEmpty());
+ assertFalse(map.isEmpty());
+ }
+
+ /**
+ * keySet returns a Set containing all the keys
+ */
+ public void testKeySet() {
+ Jsr166HashMap map = map5();
+ Set s = map.keySet();
+ assertEquals(5, s.size());
+ assertTrue(s.contains(one));
+ assertTrue(s.contains(two));
+ assertTrue(s.contains(three));
+ assertTrue(s.contains(four));
+ assertTrue(s.contains(five));
+ }
+
+ /**
+ * keySet.toArray returns contains all keys
+ */
+ public void testKeySetToArray() {
+ Jsr166HashMap map = map5();
+ Set s = map.keySet();
+ Object[] ar = s.toArray();
+ assertTrue(s.containsAll(Arrays.asList(ar)));
+ assertEquals(5, ar.length);
+ ar[0] = m10;
+ assertFalse(s.containsAll(Arrays.asList(ar)));
+ }
+
+ /**
+ * Values.toArray contains all values
+ */
+ public void testValuesToArray() {
+ Jsr166HashMap map = map5();
+ Collection v = map.values();
+ Object[] ar = v.toArray();
+ ArrayList s = new ArrayList(Arrays.asList(ar));
+ assertEquals(5, ar.length);
+ assertTrue(s.contains("A"));
+ assertTrue(s.contains("B"));
+ assertTrue(s.contains("C"));
+ assertTrue(s.contains("D"));
+ assertTrue(s.contains("E"));
+ }
+
+ /**
+ * entrySet.toArray contains all entries
+ */
+ public void testEntrySetToArray() {
+ Jsr166HashMap map = map5();
+ Set s = map.entrySet();
+ Object[] ar = s.toArray();
+ assertEquals(5, ar.length);
+ for (int i = 0; i < 5; ++i) {
+ assertTrue(map.containsKey(((Map.Entry) (ar[i])).getKey()));
+ assertTrue(map.containsValue(((Map.Entry) (ar[i])).getValue()));
+ }
+ }
+
+ /**
+ * values collection contains all values
+ */
+ public void testValues() {
+ Jsr166HashMap map = map5();
+ Collection s = map.values();
+ assertEquals(5, s.size());
+ assertTrue(s.contains("A"));
+ assertTrue(s.contains("B"));
+ assertTrue(s.contains("C"));
+ assertTrue(s.contains("D"));
+ assertTrue(s.contains("E"));
+ }
+
+ /**
+ * entrySet contains all pairs
+ */
+ public void testEntrySet() {
+ Jsr166HashMap map = map5();
+ Set s = map.entrySet();
+ assertEquals(5, s.size());
+ Iterator it = s.iterator();
+ while (it.hasNext()) {
+ Map.Entry e = (Map.Entry) it.next();
+ assertTrue(
+ (e.getKey().equals(one) && e.getValue().equals("A")) ||
+ (e.getKey().equals(two) && e.getValue().equals("B"))
+ ||
+ (e.getKey().equals(three) && e.getValue()
+ .equals("C")) ||
+ (e.getKey().equals(four) && e.getValue()
+ .equals("D")) ||
+ (e.getKey().equals(five) && e.getValue()
+ .equals("E")));
+ }
+ }
+
+ /**
+ * putAll adds all key-value pairs from the given map
+ */
+ public void testPutAll() {
+ Jsr166HashMap empty = new Jsr166HashMap();
+ Jsr166HashMap map = map5();
+ empty.putAll(map);
+ assertEquals(5, empty.size());
+ assertTrue(empty.containsKey(one));
+ assertTrue(empty.containsKey(two));
+ assertTrue(empty.containsKey(three));
+ assertTrue(empty.containsKey(four));
+ assertTrue(empty.containsKey(five));
+ }
+
+ /**
+ * putIfAbsent works when the given key is not present
+ */
+ public void testPutIfAbsent() {
+ Jsr166HashMap map = map5();
+ map.putIfAbsent(six, "Z");
+ assertTrue(map.containsKey(six));
+ }
+
+ /**
+ * putIfAbsent does not add the pair if the key is already present
+ */
+ public void testPutIfAbsent2() {
+ Jsr166HashMap map = map5();
+ assertEquals("A", map.putIfAbsent(one, "Z"));
+ }
+
+ /**
+ * replace fails when the given key is not present
+ */
+ public void testReplace() {
+ Jsr166HashMap map = map5();
+ assertNull(map.replace(six, "Z"));
+ assertFalse(map.containsKey(six));
+ }
+
+ /**
+ * replace succeeds if the key is already present
+ */
+ public void testReplace2() {
+ Jsr166HashMap map = map5();
+ assertNotNull(map.replace(one, "Z"));
+ assertEquals("Z", map.get(one));
+ }
+
+
+ /**
+ * replace value fails when the given key not mapped to expected value
+ */
+ public void testReplaceValue() {
+ Jsr166HashMap map = map5();
+ assertEquals("A", map.get(one));
+ assertFalse(map.replace(one, "Z", "Z"));
+ assertEquals("A", map.get(one));
+ }
+
+ /**
+ * replace value succeeds when the given key mapped to expected value
+ */
+ public void testReplaceValue2() {
+ Jsr166HashMap map = map5();
+ assertEquals("A", map.get(one));
+ assertTrue(map.replace(one, "A", "Z"));
+ assertEquals("Z", map.get(one));
+ }
+
+
+ /**
+ * remove removes the correct key-value pair from the map
+ */
+ public void testRemove() {
+ Jsr166HashMap map = map5();
+ map.remove(five);
+ assertEquals(4, map.size());
+ assertFalse(map.containsKey(five));
+ }
+
+ /**
+ * remove(key,value) removes only if pair present
+ */
+ public void testRemove2() {
+ Jsr166HashMap map = map5();
+ map.remove(five, "E");
+ assertEquals(4, map.size());
+ assertFalse(map.containsKey(five));
+ map.remove(four, "A");
+ assertEquals(4, map.size());
+ assertTrue(map.containsKey(four));
+
+ }
+
+ /**
+ * size returns the correct values
+ */
+ public void testSize() {
+ Jsr166HashMap map = map5();
+ Jsr166HashMap empty = new Jsr166HashMap();
+ assertEquals(0, empty.size());
+ assertEquals(5, map.size());
+ }
+
+ /**
+ * toString contains toString of elements
+ */
+ public void testToString() {
+ Jsr166HashMap map = map5();
+ String s = map.toString();
+ for (int i = 1; i <= 5; ++i) {
+ assertTrue(s.indexOf(String.valueOf(i)) >= 0);
+ }
+ }
+
+ // Exception tests
+
+ /**
+ * Cannot create with negative capacity
+ */
+ public void testConstructor1() {
+ try {
+ new Jsr166HashMap(-1, 0, 1);
+ shouldThrow();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ /**
+ * Cannot create with negative concurrency level
+ */
+ public void testConstructor2() {
+ try {
+ new Jsr166HashMap(1, 0, -1);
+ shouldThrow();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ /**
+ * Cannot create with only negative capacity
+ */
+ public void testConstructor3() {
+ try {
+ new Jsr166HashMap(-1);
+ shouldThrow();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ /**
+ * get(null) throws NPE
+ */
+ public void testGet_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.get(null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * containsKey(null) throws NPE
+ */
+ public void testContainsKey_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.containsKey(null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * containsValue(null) throws NPE
+ */
+ public void testContainsValue_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.containsValue(null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * put(null,x) throws NPE
+ */
+ public void testPut1_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.put(null, "whatever");
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * put(x, null) throws NPE
+ */
+ public void testPut2_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.put("whatever", null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * putIfAbsent(null, x) throws NPE
+ */
+ public void testPutIfAbsent1_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.putIfAbsent(null, "whatever");
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * replace(null, x) throws NPE
+ */
+ public void testReplace_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.replace(null, "whatever");
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * replace(null, x, y) throws NPE
+ */
+ public void testReplaceValue_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.replace(null, one, "whatever");
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * putIfAbsent(x, null) throws NPE
+ */
+ public void testPutIfAbsent2_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.putIfAbsent("whatever", null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+
+ /**
+ * replace(x, null) throws NPE
+ */
+ public void testReplace2_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.replace("whatever", null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * replace(x, null, y) throws NPE
+ */
+ public void testReplaceValue2_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.replace("whatever", null, "A");
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * replace(x, y, null) throws NPE
+ */
+ public void testReplaceValue3_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.replace("whatever", one, null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+
+ /**
+ * remove(null) throws NPE
+ */
+ public void testRemove1_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.put("sadsdf", "asdads");
+ c.remove(null);
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * remove(null, x) throws NPE
+ */
+ public void testRemove2_NullPointerException() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.put("sadsdf", "asdads");
+ c.remove(null, "whatever");
+ shouldThrow();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ /**
+ * remove(x, null) returns false
+ */
+ public void testRemove3() {
+ try {
+ Jsr166HashMap c = new Jsr166HashMap(5);
+ c.put("sadsdf", "asdads");
+ assertFalse(c.remove("sadsdf", null));
+ } catch (NullPointerException e) {
+ fail();
+ }
+ }
+
+ /**
+ * A deserialized map equals original
+ */
+ public void testSerialization() {
+ Jsr166HashMap q = map5();
+
+ try {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(10000);
+ ObjectOutputStream out =
+ new ObjectOutputStream(new BufferedOutputStream(bout));
+ out.writeObject(q);
+ out.close();
+
+ ByteArrayInputStream bin =
+ new ByteArrayInputStream(bout.toByteArray());
+ ObjectInputStream in =
+ new ObjectInputStream(new BufferedInputStream(bin));
+ Jsr166HashMap r = (Jsr166HashMap) in.readObject();
+ assertEquals(q.size(), r.size());
+ assertTrue(q.equals(r));
+ assertTrue(r.equals(q));
+ } catch (Exception e) {
+ e.printStackTrace();
+ unexpectedException();
+ }
+ }
+
+
+ /**
+ * SetValue of an EntrySet entry sets value in the map.
+ */
+ public void testSetValueWriteThrough() {
+ // Adapted from a bug report by Eric Zoerner
+ Jsr166HashMap map = new Jsr166HashMap(2, 5.0f, 1);
+ assertTrue(map.isEmpty());
+ for (int i = 0; i < 20; i++) {
+ map.put(new Integer(i), new Integer(i));
+ }
+ assertFalse(map.isEmpty());
+ Map.Entry entry1 = (Map.Entry) map.entrySet().iterator().next();
+
+ // assert that entry1 is not 16
+ assertTrue("entry is 16, test not valid",
+ !entry1.getKey().equals(new Integer(16)));
+
+ // remove 16 (a different key) from map
+ // which just happens to cause entry1 to be cloned in map
+ map.remove(new Integer(16));
+ entry1.setValue("XYZ");
+ assertTrue(map.containsValue("XYZ")); // fails
+ }
+
+}
diff --git a/test/com/google/inject/internal/MapMakerTestSuite.java b/test/com/google/inject/internal/MapMakerTestSuite.java
new file mode 100644
index 0000000..cb548a5
--- /dev/null
+++ b/test/com/google/inject/internal/MapMakerTestSuite.java
@@ -0,0 +1,1270 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import com.google.inject.internal.CustomConcurrentHashMap.Impl;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit tests for MapMaker. Also less directly serves as the test suite for
+ * CustomConcurrentHashMap.
+ */
+public class MapMakerTestSuite extends TestCase {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+
+ suite.addTestSuite(RecursiveComputationTest.class);
+ suite.addTestSuite(ReferenceMapTest.class);
+ suite.addTestSuite(ComputingTest.class);
+ suite.addTest(ReferenceCombinationTestSuite.suite());
+ suite.addTestSuite(ExpiringReferenceMapTest.class);
+ suite.addTestSuite(ExpiringComputingReferenceMapTest.class);
+
+ return suite;
+ }
+
+ public static class MakerTest extends TestCase {
+ public void testSizingDefaults() {
+ Impl<?, ?, ?> map = makeCustomMap(new MapMaker());
+ assertEquals(16, map.segments.length); // concurrency level
+ assertEquals(1, map.segments[0].table.length()); // capacity / conc level
+ assertEquals(0.75f, map.loadFactor);
+ }
+
+ public void testInitialCapacity_small() {
+ MapMaker maker = new MapMaker().initialCapacity(17);
+ Impl<?, ?, ?> map = makeCustomMap(maker);
+
+ assertEquals(2, map.segments[0].table.length());
+ }
+
+ public void testInitialCapacity_smallest() {
+ MapMaker maker = new MapMaker().initialCapacity(0);
+ Impl<?, ?, ?> map = makeCustomMap(maker);
+
+ // 1 is as low as it goes, not 0. it feels dirty to know this/test this.
+ assertEquals(1, map.segments[0].table.length());
+ }
+
+ public void testInitialCapacity_large() {
+ MapMaker maker = new MapMaker().initialCapacity(Integer.MAX_VALUE);
+ // that the maker didn't blow up is enough;
+ // don't actually create this monster!
+ }
+
+ public void testInitialCapacity_negative() {
+ MapMaker maker = new MapMaker();
+ try {
+ maker.initialCapacity(-1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ // TODO: enable when ready
+ public void xtestInitialCapacity_setTwice() {
+ MapMaker maker = new MapMaker().initialCapacity(16);
+ try {
+ // even to the same value is not allowed
+ maker.initialCapacity(16);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testLoadFactor_small() {
+ MapMaker maker = new MapMaker().loadFactor(Float.MIN_VALUE);
+ Impl<?, ?, ?> map = makeCustomMap(maker);
+ assertEquals(Float.MIN_VALUE, map.loadFactor);
+
+ // has no other effect until we add an entry (which would be bad)
+ assertEquals(1, map.segments[0].table.length());
+ }
+
+ public void testLoadFactor_large() {
+ MapMaker maker = new MapMaker().loadFactor(Float.MAX_VALUE);
+ Impl<?, ?, ?> map = makeCustomMap(maker);
+ assertEquals(Float.MAX_VALUE, map.loadFactor);
+
+ // these tables will never grow... we could add a ton of entries to
+ // check that if we wanted to.
+ assertEquals(1, map.segments[0].table.length());
+ }
+
+ public void testLoadFactor_zero() {
+ MapMaker maker = new MapMaker();
+ try {
+ maker.loadFactor(0);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ // TODO: enable when ready
+ public void xtestLoadFactor_setTwice() {
+ MapMaker maker = new MapMaker().loadFactor(0.75f);
+ try {
+ // even to the same value is not allowed
+ maker.loadFactor(0.75f);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testConcurrencyLevel_small() {
+ MapMaker maker = new MapMaker().concurrencyLevel(1);
+ Impl<?, ?, ?> map = makeCustomMap(maker);
+ assertEquals(1, map.segments.length);
+ }
+
+ public void testConcurrencyLevel_large() {
+ MapMaker maker = new MapMaker().concurrencyLevel(Integer.MAX_VALUE);
+ // don't actually build this beast
+ }
+
+ public void testConcurrencyLevel_zero() {
+ MapMaker maker = new MapMaker();
+ try {
+ maker.concurrencyLevel(0);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ // TODO: enable when ready
+ public void xtestConcurrencyLevel_setTwice() {
+ MapMaker maker = new MapMaker().concurrencyLevel(16);
+ try {
+ // even to the same value is not allowed
+ maker.concurrencyLevel(16);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testKeyStrengthSetTwice() {
+ MapMaker maker1 = new MapMaker().weakKeys();
+ try {
+ maker1.weakKeys();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ MapMaker maker2 = new MapMaker().softKeys();
+ try {
+ maker2.softKeys();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ MapMaker maker3 = new MapMaker().weakKeys();
+ try {
+ maker3.softKeys();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testValueStrengthSetTwice() {
+ MapMaker maker1 = new MapMaker().weakValues();
+ try {
+ maker1.weakValues();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ MapMaker maker2 = new MapMaker().softValues();
+ try {
+ maker2.softValues();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ MapMaker maker3 = new MapMaker().weakValues();
+ try {
+ maker3.softValues();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testExpiration_small() {
+ MapMaker maker = new MapMaker().expiration(1, NANOSECONDS);
+ // well, it didn't blow up.
+ }
+
+// public void testExpiration_setTwice() {
+// MapMaker maker = new MapMaker().expiration(1, HOURS);
+// try {
+// // even to the same value is not allowed
+// maker.expiration(1, HOURS);
+// fail();
+// } catch (IllegalStateException expected) {
+// }
+// }
+
+ public void testReturnsPlainConcurrentHashMapWhenPossible() {
+ Map<?, ?> map = new MapMaker()
+ .concurrencyLevel(5)
+ .loadFactor(0.5f)
+ .initialCapacity(5)
+ .makeMap();
+ assertTrue(map instanceof ConcurrentHashMap);
+ }
+
+ private static Impl<?, ?, ?> makeCustomMap(MapMaker maker) {
+ // Use makeComputingMap() to force it to return CCHM.Impl, not
+ // ConcurrentHashMap.
+ return (Impl<?, ?, ?>) maker.makeComputingMap(new Function<Object, Object>() {
+ public Object apply(Object from) {
+ return from;
+ }
+ });
+ }
+ }
+
+ public static class RecursiveComputationTest extends TestCase {
+
+ Function<Integer, String> recursiveComputer
+ = new Function<Integer, String>() {
+ public String apply(Integer key) {
+ if (key > 0) {
+ return key + ", " + recursiveMap.get(key - 1);
+ } else {
+ return "0";
+ }
+ }
+ };
+
+ ConcurrentMap<Integer, String> recursiveMap = new MapMaker()
+ .weakKeys()
+ .weakValues()
+ .makeComputingMap(recursiveComputer);
+
+ public void testRecursiveComputation() {
+ assertEquals("3, 2, 1, 0", recursiveMap.get(3));
+ }
+ }
+
+ /**
+ * Tests for basic map functionality.
+ */
+ public static class ReferenceMapTest extends TestCase {
+
+ public void testValueCleanupWithWeakKey() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().weakKeys().makeMap();
+ map.put(new Object(), new Object());
+ assertCleanup(map);
+ }
+
+ public void testValueCleanupWithSoftKey() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().softKeys().makeMap();
+ map.put(new Object(), new Object());
+ assertCleanup(map);
+ }
+
+ public void testKeyCleanupWithWeakValue() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().weakValues().makeMap();
+ map.put(new Object(), new Object());
+ assertCleanup(map);
+ }
+
+ public void testKeyCleanupWithSoftValue() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().softValues().makeMap();
+ map.put(new Object(), new Object());
+ assertCleanup(map);
+ }
+
+ public void testInternedValueCleanupWithWeakKey() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().weakKeys().makeMap();
+ map.put(new Integer(5), "foo");
+ assertCleanup(map);
+ }
+
+ public void testInternedValueCleanupWithSoftKey() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().softKeys().makeMap();
+ map.put(new Integer(5), "foo");
+ assertCleanup(map);
+ }
+
+ public void testInternedKeyCleanupWithWeakValue() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().weakValues().makeMap();
+ map.put(5, new String("foo"));
+ assertCleanup(map);
+ }
+
+ public void testInternedKeyCleanupWithSoftValue() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().softValues().makeMap();
+ map.put(5, new String("foo"));
+ assertCleanup(map);
+ }
+
+ public void testReplace() {
+ ConcurrentMap<Object, Object> map =
+ new MapMaker().makeMap();
+ assertNull(map.replace("one", 1));
+ }
+
+ private static void assertCleanup(ConcurrentMap<?, ?> map) {
+ assertEquals(1, map.size());
+
+ // wait up to 5s
+ byte[] filler = new byte[1024];
+ for (int i = 0; i < 500; i++) {
+ System.gc();
+ if (map.isEmpty()) {
+ return;
+ }
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) { /* ignore */ }
+ try {
+ // Fill up heap so soft references get cleared.
+ filler = new byte[filler.length * 2];
+ } catch (OutOfMemoryError e) {}
+ }
+
+ fail();
+ }
+
+ public void testWeakKeyIdentityLookup() {
+ ConcurrentMap<Integer, String> map =
+ new MapMaker().weakKeys().makeMap();
+ Integer key1 = new Integer(12357);
+ Integer key2 = new Integer(12357);
+ map.put(key1, "a");
+ assertTrue(map.containsKey(key1));
+ assertFalse(map.containsKey(key2));
+ }
+
+ public void testSoftKeyIdentityLookup() {
+ ConcurrentMap<Integer, String> map =
+ new MapMaker().softKeys().makeMap();
+ Integer key1 = new Integer(12357);
+ Integer key2 = new Integer(12357);
+ map.put(key1, "a");
+ assertTrue(map.containsKey(key1));
+ assertFalse(map.containsKey(key2));
+ }
+
+ public void testWeakValueIdentityLookup() {
+ ConcurrentMap<String, Integer> map =
+ new MapMaker().weakValues().makeMap();
+ Integer value1 = new Integer(12357);
+ Integer value2 = new Integer(12357);
+ map.put("a", value1);
+ assertTrue(map.containsValue(value1));
+ assertFalse(map.containsValue(value2));
+ }
+
+ public void testSoftValueIdentityLookup() {
+ ConcurrentMap<String, Integer> map =
+ new MapMaker().softValues().makeMap();
+ Integer value1 = new Integer(12357);
+ Integer value2 = new Integer(12357);
+ map.put("a", value1);
+ assertTrue(map.containsValue(value1));
+ assertFalse(map.containsValue(value2));
+ }
+
+ public void testWeakKeyEntrySetRemove() {
+ ConcurrentMap<Integer, String> map =
+ new MapMaker().weakKeys().makeMap();
+ Integer key1 = new Integer(12357);
+ Integer key2 = new Integer(12357);
+ map.put(key1, "a");
+ assertFalse(map.entrySet().remove(entry(key2, "a")));
+ assertEquals(1, map.size());
+ assertTrue(map.entrySet().remove(entry(key1, "a")));
+ assertEquals(0, map.size());
+ }
+
+ public void testEntrySetIteratorRemove() {
+ ConcurrentMap<String, Integer> map =
+ new MapMaker().makeMap();
+ map.put("foo", 1);
+ map.put("bar", 2);
+ assertEquals(2, map.size());
+ Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
+ try {
+ iterator.remove();
+ fail();
+ } catch (IllegalStateException expected) {}
+ iterator.next();
+ iterator.remove();
+ assertEquals(1, map.size());
+ try {
+ iterator.remove();
+ fail();
+ } catch (IllegalStateException expected) {}
+ iterator.next();
+ iterator.remove();
+ assertEquals(0, map.size());
+ }
+
+ public void testSerialization() {
+ ConcurrentMap<String, Integer> map =
+ new MapMaker().makeMap();
+ map.put("one", 1);
+ reserializeAndAssert(map);
+
+ map = new MapMaker().weakKeys().makeMap();
+ map.put("one", 1);
+ reserialize(map);
+ }
+ }
+
+ /**
+ * Tests for computing functionality.
+ */
+ public static class ComputingTest extends TestCase {
+
+ public void testComputerThatReturnsNull() {
+ ConcurrentMap<Integer, String> map = new MapMaker()
+ .makeComputingMap(new Function<Integer, String>() {
+ public String apply(Integer key) {
+ return null;
+ }
+ });
+ try {
+ map.get(1);
+ fail();
+ } catch (NullPointerException e) { /* expected */ }
+ }
+
+ public void testRecomputeAfterReclamation()
+ throws InterruptedException {
+ ConcurrentMap<Integer, String> map = new MapMaker()
+ .weakValues()
+ .makeComputingMap(new Function<Integer, String>() {
+ @SuppressWarnings("RedundantStringConstructorCall")
+ public String apply(Integer key) {
+ return new String("one");
+ }
+ });
+
+ for (int i = 0; i < 10; i++) {
+ // The entry should get garbage collected and recomputed.
+ assertEquals("on iteration " + i, "one", map.get(1));
+ Thread.sleep(i);
+ System.gc();
+ }
+ }
+
+ public void testRuntimeException() {
+ final RuntimeException e = new RuntimeException();
+
+ ConcurrentMap<Object, Object> map = new MapMaker().makeComputingMap(
+ new Function<Object, Object>() {
+ public Object apply(Object from) {
+ throw e;
+ }
+ });
+
+ try {
+ map.get(new Object());
+ fail();
+ } catch (ComputationException ce) {
+ assertSame(e, ce.getCause());
+ }
+ }
+
+ public void testSleepConcurrency() throws InterruptedException {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .weakKeys().weakValues().makeComputingMap(new SleepFunction());
+ assertConcurrency(cache, false);
+ }
+
+ public void testBusyConcurrency() throws InterruptedException {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .weakKeys().weakValues().makeComputingMap(new BusyFunction());
+ assertConcurrency(cache, false);
+ }
+
+ public void testFastConcurrency() throws InterruptedException {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .weakKeys().weakValues().makeComputingMap(new SomeFunction());
+ assertConcurrency(cache, false);
+ }
+
+ public void testSleepCanonical() throws InterruptedException {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .softValues().makeComputingMap(new SleepFunction());
+ assertConcurrency(cache, true);
+ }
+
+ public void testBusyCanonical() throws InterruptedException {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .softValues().makeComputingMap(new BusyFunction());
+ assertConcurrency(cache, true);
+ }
+
+ public void testFastCanonical() throws InterruptedException {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .softValues().makeComputingMap(new SomeFunction());
+ assertConcurrency(cache, true);
+ }
+
+ private static void assertConcurrency(
+ final ConcurrentMap<String, Integer> cache,
+ final boolean simulateAliasing) throws InterruptedException {
+ final int n = 20;
+ final CountDownLatch startSignal = new CountDownLatch(1);
+ final CountDownLatch doneSignal = new CountDownLatch(n);
+ for (int i = 0; i < n; i++) {
+ new Thread() {
+ @Override public void run() {
+ try {
+ startSignal.await();
+ for (int j = 0; j < n; j++) {
+ cache.get(simulateAliasing ? new String("foo") : "foo");
+ }
+ doneSignal.countDown();
+ } catch (InterruptedException ignored) {}
+ }
+ }.start();
+ }
+
+ startSignal.countDown();
+ doneSignal.await();
+ assertEquals(Integer.valueOf(1), cache.get("foo"));
+ assertEquals(Integer.valueOf(2), cache.get("bar"));
+ }
+
+ private static class SomeFunction implements Function<String, Integer> {
+ private int numApplies = 0;
+ public Integer apply(String s) {
+ return ++numApplies;
+ }
+ }
+
+ private static class SleepFunction implements Function<String, Integer> {
+ private int numApplies = 0;
+ public Integer apply(String s) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return ++numApplies;
+ }
+ }
+
+ private static class BusyFunction implements Function<String, Integer> {
+ private int numApplies = 0;
+ public Integer apply(String s) {
+ for (int i = 0; i < 1000; i++) {
+ Math.sqrt(i);
+ }
+ return ++numApplies;
+ }
+ }
+ }
+
+ /**
+ * Tests combinations of key and value reference types.
+ */
+ public static class ReferenceCombinationTestSuite {
+
+ interface BuilderOption {
+ void applyTo(MapMaker maker);
+ }
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+
+ BuilderOption[] keyOptions = {
+ new BuilderOption() {
+ public void applyTo(MapMaker maker) {
+ // strong keys
+ }
+ @Override public String toString() {
+ return "Strong";
+ }
+ },
+ new BuilderOption() {
+ public void applyTo(MapMaker maker) {
+ maker.weakKeys();
+ }
+ @Override public String toString() {
+ return "Weak";
+ }
+ },
+ new BuilderOption() {
+ public void applyTo(MapMaker maker) {
+ maker.softKeys();
+ }
+ @Override public String toString() {
+ return "Soft";
+ }
+ },
+ };
+
+ BuilderOption[] valueOptions = {
+ new BuilderOption() {
+ public void applyTo(MapMaker maker) {
+ // strong values
+ }
+ @Override public String toString() {
+ return "Strong";
+ }
+ },
+ new BuilderOption() {
+ public void applyTo(MapMaker maker) {
+ maker.weakValues();
+ }
+ @Override public String toString() {
+ return "Weak";
+ }
+ },
+ new BuilderOption() {
+ public void applyTo(MapMaker maker) {
+ maker.softValues();
+ }
+ @Override public String toString() {
+ return "Soft";
+ }
+ },
+ };
+
+ // create test cases for each key and value type.
+ for (Method method : MapTest.class.getMethods()) {
+ String name = method.getName();
+ if (name.startsWith("test")) {
+ for (BuilderOption keyOption : keyOptions) {
+ for (BuilderOption valueOption : valueOptions) {
+ suite.addTest(new MapTest(name, keyOption, valueOption));
+ }
+ }
+ }
+ }
+
+ return suite;
+ }
+
+ public static class MapTest extends TestCase {
+
+ final BuilderOption keyOption;
+ final BuilderOption valueOption;
+
+ public MapTest(String name,
+ BuilderOption keyOption,
+ BuilderOption valueOption) {
+ super(name);
+ this.keyOption = keyOption;
+ this.valueOption = valueOption;
+ }
+
+ @Override public String getName() {
+ return super.getName() + "For" + keyOption + valueOption;
+ }
+
+ MapMaker newBuilder() {
+ MapMaker maker = new MapMaker();
+ keyOption.applyTo(maker);
+ valueOption.applyTo(maker);
+ return maker;
+ }
+
+ <K, V> ConcurrentMap<K, V> newMap() {
+ MapMaker maker = new MapMaker();
+ keyOption.applyTo(maker);
+ valueOption.applyTo(maker);
+ return maker.makeMap();
+ }
+
+ public void testContainsKey() {
+ ConcurrentMap<Object, String> map = newMap();
+ Object k = "key";
+ map.put(k, "value");
+ assertTrue(map.containsKey(k));
+ }
+
+ public void testClear() {
+ ConcurrentMap<String, String> map = newMap();
+ String k = "key";
+ map.put(k, "value");
+ assertFalse(map.isEmpty());
+ map.clear();
+ assertTrue(map.isEmpty());
+ assertNull(map.get(k));
+ }
+
+ public void testKeySet() {
+ ConcurrentMap<String, String> map = newMap();
+ map.put("a", "foo");
+ map.put("b", "foo");
+ Set<String> expected = set("a", "b");
+ assertEquals(expected, map.keySet());
+ }
+
+ public void testValues() {
+ ConcurrentMap<String, String> map = newMap();
+ map.put("a", "1");
+ map.put("b", "2");
+ Set<String> expected = set("1", "2");
+ Set<String> actual = new HashSet<String>();
+ actual.addAll(map.values());
+ assertEquals(expected, actual);
+ }
+
+ public void testPutIfAbsent() {
+ ConcurrentMap<String, String> map = newMap();
+ map.putIfAbsent("a", "1");
+ assertEquals("1", map.get("a"));
+ map.putIfAbsent("a", "2");
+ assertEquals("1", map.get("a"));
+ }
+
+ public void testReplace() {
+ ConcurrentMap<String, String> map = newMap();
+ map.put("a", "1");
+ map.replace("a", "2", "2");
+ assertEquals("1", map.get("a"));
+ map.replace("a", "1", "2");
+ assertEquals("2", map.get("a"));
+ }
+
+ public void testContainsValue() {
+ ConcurrentMap<String, Object> map = newMap();
+ Object v = "value";
+ map.put("key", v);
+ assertTrue(map.containsValue(v));
+ }
+
+ public void testEntrySet() {
+ final ConcurrentMap<String, String> map = newMap();
+ map.put("a", "1");
+ map.put("b", "2");
+ Set<Map.Entry<String, String>> expected
+ = set(entry("a", "1"), entry("b", "2"));
+ assertEquals(expected, map.entrySet());
+ }
+
+ public void testPutAll() {
+ ConcurrentMap<Object, Object> map = newMap();
+ Object k = "key";
+ Object v = "value";
+ map.putAll(Collections.singletonMap(k, v));
+ assertSame(v, map.get(k));
+ }
+
+ public void testRemove() {
+ ConcurrentMap<Object, String> map = newMap();
+ Object k = "key";
+ map.put(k, "value");
+ map.remove(k);
+ assertFalse(map.containsKey(k));
+ }
+
+ public void testPutGet() {
+ final Object k = new Object();
+ final Object v = new Object();
+ ConcurrentMap<Object, Object> map = newMap();
+ map.put(k, v);
+ assertEquals(1, map.size());
+ assertSame(v, map.get(k));
+ assertEquals(1, map.size());
+ assertNull(map.get(new Object()));
+ }
+
+ public void testCompute() {
+ final Object k = new Object();
+ final Object v = new Object();
+ ConcurrentMap<?, ?> map = newBuilder().makeComputingMap(
+ new Function<Object, Object>() {
+ public Object apply(Object key) {
+ return key == k ? v : null;
+ }
+ });
+
+ assertEquals(0, map.size());
+ assertSame(v, map.get(k));
+ assertSame(v, map.get(k));
+ assertEquals(1, map.size());
+
+ try {
+ map.get(new Object());
+ fail();
+ } catch (NullPointerException e) { /* expected */ }
+ assertEquals(1, map.size());
+ }
+
+ public void testReferenceMapSerialization() throws IOException,
+ ClassNotFoundException {
+ Map<Key, Value> original = newMap();
+ original.put(Key.FOO, Value.FOO);
+ @SuppressWarnings("unchecked")
+ Map<Key, Value> map = reserialize(original);
+ map.put(Key.BAR, Value.BAR);
+ assertSame(Value.FOO, map.get(Key.FOO));
+ assertSame(Value.BAR, map.get(Key.BAR));
+ assertNull(map.get(Key.TEE));
+ }
+
+ static class MockFunction implements Function<Object, Object>,
+ Serializable {
+ int count;
+ public Object apply(Object key) {
+ count++;
+ return Value.valueOf(key.toString());
+ }
+ }
+
+ public void testReferenceCacheSerialization() throws IOException,
+ ClassNotFoundException {
+ MockFunction f = new MockFunction();
+ ConcurrentMap<Object, Object> map = newBuilder().makeComputingMap(f);
+ assertSame(Value.FOO, map.get(Key.FOO));
+ assertSame(Value.BAR, map.get(Key.BAR));
+ map = reserialize(map);
+ assertSame(Value.FOO, map.get(Key.FOO));
+ assertSame(Value.BAR, map.get(Key.BAR));
+ assertSame(Value.TEE, map.get(Key.TEE));
+ assertEquals(2, f.count);
+ }
+ }
+
+ /**
+ * Enums conveniently maintain instance identity across serialization.
+ */
+ enum Key {
+ FOO, BAR, TEE
+ }
+
+ enum Value {
+ FOO, BAR, TEE
+ }
+ }
+
+ public static class ExpiringReferenceMapTest extends TestCase {
+
+ private static final long EXPIRING_TIME = 10;
+ private static final int VALUE_PREFIX = 12345;
+ private static final String KEY_PREFIX = "key prefix:";
+
+ Timer oldTimer;
+ final List<TimerTask> tasks = new ArrayList<TimerTask>();
+
+ @Override
+ protected void setUp() throws Exception {
+ oldTimer = ExpirationTimer.instance;
+ ExpirationTimer.instance = new Timer() {
+ @Override
+ public void schedule(TimerTask task, long delay) {
+ tasks.add(task);
+ }
+ };
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ExpirationTimer.instance = oldTimer;
+ }
+
+ private void runTasks() {
+ for (TimerTask task : tasks) {
+ task.run();
+ }
+ tasks.clear();
+ }
+
+ public void testExpiringPut() {
+ ConcurrentMap<String, Integer> map = new MapMaker()
+ .expiration(EXPIRING_TIME, TimeUnit.MILLISECONDS).makeMap();
+
+ for (int i = 0; i < 10; i++) {
+ map.put(KEY_PREFIX + i, VALUE_PREFIX + i);
+ assertEquals(Integer.valueOf(VALUE_PREFIX + i),
+ map.get(KEY_PREFIX + i));
+ }
+
+ runTasks();
+
+ assertEquals("Map must be empty by now", 0, map.size());
+ }
+
+ public void testExpiringPutIfAbsent() {
+ ConcurrentMap<String, Integer> map = new MapMaker()
+ .expiration(EXPIRING_TIME, TimeUnit.MILLISECONDS).makeMap();
+
+ for (int i = 0; i < 10; i++) {
+ map.putIfAbsent(KEY_PREFIX + i, VALUE_PREFIX + i);
+ assertEquals(Integer.valueOf(VALUE_PREFIX + i), map.get(KEY_PREFIX + i));
+ }
+
+ runTasks();
+
+ assertEquals("Map must be empty by now", 0, map.size());
+ }
+
+ public void testExpiringGetForSoft() {
+ ConcurrentMap<String, Integer> map = new MapMaker()
+ .expiration(EXPIRING_TIME, TimeUnit.MILLISECONDS).makeMap();
+
+ runExpirationTest(map);
+ }
+
+ public void testExpiringGetForStrong() {
+ ConcurrentMap<String, Integer> map = new MapMaker()
+ .expiration(EXPIRING_TIME, TimeUnit.MILLISECONDS).makeMap();
+
+ runExpirationTest(map);
+ }
+
+ public void testRemovalSchedulerForStrong() {
+ ConcurrentMap<String, Integer> map = new MapMaker()
+ .expiration(EXPIRING_TIME, TimeUnit.MILLISECONDS).makeMap();
+
+ runRemovalScheduler(map, KEY_PREFIX, EXPIRING_TIME);
+ }
+
+ public void testRemovalSchedulerForSoft() {
+ ConcurrentMap<String, Integer> map = new MapMaker()
+ .softValues().expiration(EXPIRING_TIME, TimeUnit.MILLISECONDS).makeMap();
+
+ runRemovalScheduler(map, KEY_PREFIX, EXPIRING_TIME);
+ }
+
+ private void runExpirationTest(ConcurrentMap<String, Integer> map) {
+ for (int i = 0; i < 10; i++) {
+ map.put(KEY_PREFIX + i, VALUE_PREFIX + i);
+ assertEquals(Integer.valueOf(VALUE_PREFIX + i),
+ map.get(KEY_PREFIX + i));
+ }
+
+ for (int i = 0; i < 10; i++) {
+ assertEquals(Integer.valueOf(i + VALUE_PREFIX),
+ map.get(KEY_PREFIX + i));
+ }
+
+ runTasks();
+
+ for (int i = 0; i < 10; i++) {
+ assertEquals(null, map.get(KEY_PREFIX + i));
+ }
+ }
+
+ private void runRemovalScheduler(ConcurrentMap<String, Integer> map,
+ String keyPrefix, long ttl) {
+
+ int shift1 = 10 + VALUE_PREFIX;
+ // fill with initial data
+ for (int i = 0; i < 10; i++) {
+ map.put(keyPrefix + i, i + shift1);
+ assertEquals(Integer.valueOf(i + shift1), map.get(keyPrefix + i));
+ }
+
+ // wait, so that entries have just 10 ms to live
+ try {
+ Thread.sleep(ttl * 2 / 3);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ int shift2 = shift1 + 10;
+ // fill with new data - has to live for 20 ms more
+ for (int i = 0; i < 10; i++) {
+ map.put(keyPrefix + i, i + shift2);
+ assertEquals("key: " + keyPrefix + i,
+ Integer.valueOf(i + shift2), map.get(keyPrefix + i));
+ }
+
+ // old timeouts must expire after this wait
+ try {
+ Thread.sleep(ttl * 2 / 3);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ // check that new values are still there - they still have 10 ms to live
+ for (int i = 0; i < 10; i++) {
+ assertEquals(Integer.valueOf(i + shift2), map.get(keyPrefix + i));
+ }
+ }
+ }
+
+ public static class ExpiringComputingReferenceMapTest extends TestCase {
+
+ static final long VERY_LONG = 100000L;
+ static final String KEY_PREFIX = "THIS IS AN ARBITRARY KEY PREFIX";
+ static final int VALUE_SUFFIX = 77777;
+
+ Timer oldTimer;
+ final List<TimerTask> tasks = new ArrayList<TimerTask>();
+
+ @Override
+ protected void setUp() throws Exception {
+ oldTimer = ExpirationTimer.instance;
+ ExpirationTimer.instance = new Timer() {
+ @Override
+ public void schedule(TimerTask task, long delay) {
+ tasks.add(task);
+ }
+ };
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ExpirationTimer.instance = oldTimer;
+ }
+
+ private void runTasks() {
+ for (TimerTask task : tasks) {
+ task.run();
+ }
+ tasks.clear();
+ }
+
+ public void testExpiringPut() {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .expiration(50, TimeUnit.MILLISECONDS).makeComputingMap(WATCHED_CREATOR);
+
+ for (int i = 0; i < 10; i++) {
+ cache.put(KEY_PREFIX + i, i + VALUE_SUFFIX);
+ assertEquals(Integer.valueOf(i + VALUE_SUFFIX),
+ cache.get(KEY_PREFIX + i));
+ }
+
+ for (int i = 0; i < 10; i++) {
+ WATCHED_CREATOR.reset();
+ assertEquals(Integer.valueOf(i + VALUE_SUFFIX),
+ cache.get(KEY_PREFIX + i));
+ assertFalse("Creator should not have been called @#" + i,
+ WATCHED_CREATOR.wasCalled());
+ }
+
+ runTasks();
+
+ assertEquals("Cache must be empty by now", 0, cache.size());
+ }
+
+ public void testExpiringPutIfAbsent() {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .expiration(50, TimeUnit.MILLISECONDS).makeComputingMap(WATCHED_CREATOR);
+
+ for (int i = 0; i < 10; i++) {
+ cache.putIfAbsent(KEY_PREFIX + i, i + VALUE_SUFFIX);
+ assertEquals(Integer.valueOf(i + VALUE_SUFFIX),
+ cache.get(KEY_PREFIX + i));
+ }
+
+ runTasks();
+
+ assertEquals("Cache must be empty by now", 0, cache.size());
+ }
+
+ public void testExpiringGetForSoft() {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .expiration(5, TimeUnit.MILLISECONDS)
+ .softValues().makeComputingMap(WATCHED_CREATOR);
+
+ runExpirationTest(cache);
+ }
+
+ public void testExpiringGetForStrong() {
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .expiration(10, TimeUnit.MILLISECONDS).makeComputingMap(WATCHED_CREATOR);
+
+ runExpirationTest(cache);
+ }
+
+ private void runExpirationTest(ConcurrentMap<String, Integer> cache) {
+ for (int i = 0; i < 10; i++) {
+ assertEquals(Integer.valueOf(i + VALUE_SUFFIX),
+ cache.get(KEY_PREFIX + i));
+ }
+
+ for (int i = 0; i < 10; i++) {
+ WATCHED_CREATOR.reset();
+ assertEquals(Integer.valueOf(i + VALUE_SUFFIX),
+ cache.get(KEY_PREFIX + i));
+ assertFalse("Creator should NOT have been called @#" + i,
+ WATCHED_CREATOR.wasCalled());
+ }
+
+ runTasks();
+
+ for (int i = 0; i < 10; i++) {
+ WATCHED_CREATOR.reset();
+ assertEquals(Integer.valueOf(i + VALUE_SUFFIX),
+ cache.get(KEY_PREFIX + i));
+ assertTrue("Creator should have been called @#" + i,
+ WATCHED_CREATOR.wasCalled());
+ }
+ }
+
+ public void testRemovalSchedulerForStrong() {
+ String keyPrefix = "TRSTRONG_";
+ int ttl = 300;
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .expiration(ttl, TimeUnit.MILLISECONDS)
+ .makeComputingMap(new WatchedCreatorFunction(keyPrefix));
+ runRemovalScheduler(cache, keyPrefix, ttl);
+ }
+
+ public void testRemovalSchedulerForSoft() {
+ String keyPrefix = "TRSOFT_";
+ int ttl = 300;
+ ConcurrentMap<String, Integer> cache = new MapMaker()
+ .expiration(ttl, TimeUnit.MILLISECONDS).softValues()
+ .makeComputingMap(new WatchedCreatorFunction(keyPrefix));
+ runRemovalScheduler(cache, keyPrefix, ttl);
+ }
+
+ private void runRemovalScheduler(ConcurrentMap<String, Integer> cache,
+ String keyPrefix, int ttl) {
+ int shift1 = 10 + VALUE_SUFFIX;
+ // fill with initial data
+ for (int i = 0; i < 10; i++) {
+ cache.put(keyPrefix + i, i + shift1);
+ assertEquals(Integer.valueOf(i + shift1), cache.get(keyPrefix + i));
+ }
+
+ // wait, so that entries have just 10 ms to live
+ try {
+ Thread.sleep(ttl * 2 / 3);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ int shift2 = shift1 + 10;
+ // fill with new data - has to live for 20 ms more
+ for (int i = 0; i < 10; i++) {
+ cache.put(keyPrefix + i, i + shift2);
+ assertEquals("key: " + keyPrefix + i,
+ Integer.valueOf(i + shift2), cache.get(keyPrefix + i));
+ }
+
+ // old timeouts must expire after this wait
+ try {
+ Thread.sleep(ttl * 2 / 3);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ // check that new values are still there - they still have 10 ms to live
+ for (int i = 0; i < 10; i++) {
+ assertEquals(Integer.valueOf(i + shift2), cache.get(keyPrefix + i));
+ }
+ }
+
+ private static class WatchedCreatorFunction
+ implements Function<String, Integer> {
+ boolean wasCalled = false; // must be set in apply()
+
+ public WatchedCreatorFunction() {
+ this(KEY_PREFIX);
+ }
+
+ public WatchedCreatorFunction(String prefix) {
+ setPrefix(prefix);
+ }
+
+ public void reset() {
+ wasCalled = false;
+ }
+
+ protected String prefix = KEY_PREFIX;
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public boolean wasCalled() {
+ return wasCalled;
+ }
+
+ public Integer apply(String s) {
+ wasCalled = true;
+ return Integer.parseInt(s.substring(prefix.length())) + VALUE_SUFFIX;
+ }
+ }
+
+ static final WatchedCreatorFunction WATCHED_CREATOR =
+ new WatchedCreatorFunction();
+ }
+
+ static <T> T reserializeAndAssert(T object) {
+ T copy = reserialize(object);
+ if (!copy.equals(object)) {
+ throw new AssertionError("The re-serialized object " + copy +
+ " does not equal the original object " + object);
+ }
+ return copy;
+ }
+
+ @SuppressWarnings("unchecked")
+ static <T> T reserialize(T object) {
+ if (object == null) {
+ throw new NullPointerException();
+ }
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream out = new ObjectOutputStream(bytes);
+ out.writeObject(object);
+ ObjectInputStream in = new ObjectInputStream(
+ new ByteArrayInputStream(bytes.toByteArray()));
+ return (T) in.readObject();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static <K, V> Map.Entry<K, V> entry(K key, V value) {
+ return new SimpleEntry<K, V>(key, value);
+ }
+
+ static <E> Set<E> set(E... elements) {
+ return new HashSet<E>(Arrays.asList(elements));
+ }
+}
diff --git a/test/com/google/inject/internal/Preconditions.java b/test/com/google/inject/internal/Preconditions.java
new file mode 100644
index 0000000..c7d67b3
--- /dev/null
+++ b/test/com/google/inject/internal/Preconditions.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.util.Collection;
+import java.util.NoSuchElementException;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state. This allows constructs such as
+ * <pre>
+ * if (count <= 0) {
+ * throw new IllegalArgumentException("must be positive: " + count);
+ * }</pre>
+ *
+ * to be replaced with the more compact
+ * <pre>
+ * checkArgument(count > 0, "must be positive: %s", count);</pre>
+ *
+ * Note that the sense of the expression is inverted; with {@code Preconditions}
+ * you declare what you expect to be <i>true</i>, just as you do with an
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html">
+ * {@code assert}</a> or a JUnit {@code assertTrue()} call.
+ *
+ * <p>Take care not to confuse precondition checking with other similar types
+ * of checks! Precondition exceptions -- including those provided here, but also
+ * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link
+ * UnsupportedOperationException} and others -- are used to signal that the
+ * <i>calling method</i> has made an error. This tells the caller that it should
+ * not have invoked the method when it did, with the arguments it did, or
+ * perhaps <i>ever</i>. Postcondition or other invariant failures should not
+ * throw these types of exceptions.
+ *
+ * <p><b>Note:</b> The methods of the {@code Preconditions} class are highly
+ * unusual in one way: they are <i>supposed to</i> throw exceptions, and promise
+ * in their specifications to do so even when given perfectly valid input. That
+ * is, {@code null} is a valid parameter to the method {@link
+ * #checkNotNull(Object)} -- and technically this parameter could be even marked
+ * as {@link Nullable} -- yet the method will still throw an exception anyway,
+ * because that's what its contract says to do.
+ *
+ * <p>This class may be used with the Google Web Toolkit (GWT).
+ *
+ * @author Kevin Bourrillion
+ */
+public final class Preconditions {
+ private Preconditions() {}
+
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(boolean expression) {
+ if (!expression) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new IllegalArgumentException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @throws IllegalArgumentException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code
+ * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
+ * this happen)
+ */
+ public static void checkArgument(boolean expression,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!expression) {
+ throw new IllegalArgumentException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ public static void checkState(boolean expression) {
+ if (!expression) {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ public static void checkState(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new IllegalStateException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @throws IllegalStateException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code
+ * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
+ * this happen)
+ */
+ public static void checkState(boolean expression,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!expression) {
+ throw new IllegalStateException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference) {
+ if (reference == null) {
+ throw new NullPointerException();
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference, Object errorMessage) {
+ if (reference == null) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an object reference
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(T reference, String errorMessageTemplate,
+ Object... errorMessageArgs) {
+ if (reference == null) {
+ // If either of these parameters is null, the right thing happens anyway
+ throw new NullPointerException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ return reference;
+ }
+
+ /**
+ * Ensures that an {@code Iterable} object passed as a parameter to the
+ * calling method is not null and contains no null elements.
+ *
+ * @param iterable the iterable to check the contents of
+ * @return the non-null {@code iterable} reference just validated
+ * @throws NullPointerException if {@code iterable} is null or contains at
+ * least one null element
+ */
+ public static <T extends Iterable<?>> T checkContentsNotNull(T iterable) {
+ if (containsOrIsNull(iterable)) {
+ throw new NullPointerException();
+ }
+ return iterable;
+ }
+
+ /**
+ * Ensures that an {@code Iterable} object passed as a parameter to the
+ * calling method is not null and contains no null elements.
+ *
+ * @param iterable the iterable to check the contents of
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @return the non-null {@code iterable} reference just validated
+ * @throws NullPointerException if {@code iterable} is null or contains at
+ * least one null element
+ */
+ public static <T extends Iterable<?>> T checkContentsNotNull(
+ T iterable, Object errorMessage) {
+ if (containsOrIsNull(iterable)) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ return iterable;
+ }
+
+ /**
+ * Ensures that an {@code Iterable} object passed as a parameter to the
+ * calling method is not null and contains no null elements.
+ *
+ * @param iterable the iterable to check the contents of
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}.
+ * @return the non-null {@code iterable} reference just validated
+ * @throws NullPointerException if {@code iterable} is null or contains at
+ * least one null element
+ */
+ public static <T extends Iterable<?>> T checkContentsNotNull(T iterable,
+ String errorMessageTemplate, Object... errorMessageArgs) {
+ if (containsOrIsNull(iterable)) {
+ throw new NullPointerException(
+ format(errorMessageTemplate, errorMessageArgs));
+ }
+ return iterable;
+ }
+
+ private static boolean containsOrIsNull(Iterable<?> iterable) {
+ if (iterable == null) {
+ return true;
+ }
+
+ if (iterable instanceof Collection) {
+ Collection<?> collection = (Collection<?>) iterable;
+ try {
+ return collection.contains(null);
+ } catch (NullPointerException e) {
+ // A NPE implies that the collection doesn't contain null.
+ return false;
+ }
+ } else {
+ for (Object element : iterable) {
+ if (element == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>element</i> in an array,
+ * list or string of size {@code size}. An element index may range from zero,
+ * inclusive, to {@code size}, exclusive.
+ *
+ * @param index a user-supplied index identifying an element of an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is not
+ * less than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static void checkElementIndex(int index, int size) {
+ checkElementIndex(index, size, "index");
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>element</i> in an array,
+ * list or string of size {@code size}. An element index may range from zero,
+ * inclusive, to {@code size}, exclusive.
+ *
+ * @param index a user-supplied index identifying an element of an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @param desc the text to use to describe this index in an error message
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is not
+ * less than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static void checkElementIndex(int index, int size, String desc) {
+ checkArgument(size >= 0, "negative size: %s", size);
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(
+ format("%s (%s) must not be negative", desc, index));
+ }
+ if (index >= size) {
+ throw new IndexOutOfBoundsException(
+ format("%s (%s) must be less than size (%s)", desc, index, size));
+ }
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>position</i> in an array,
+ * list or string of size {@code size}. A position index may range from zero
+ * to {@code size}, inclusive.
+ *
+ * @param index a user-supplied index identifying a position in an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is
+ * greater than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static void checkPositionIndex(int index, int size) {
+ checkPositionIndex(index, size, "index");
+ }
+
+ /**
+ * Ensures that {@code index} specifies a valid <i>position</i> in an array,
+ * list or string of size {@code size}. A position index may range from zero
+ * to {@code size}, inclusive.
+ *
+ * @param index a user-supplied index identifying a position in an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @param desc the text to use to describe this index in an error message
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is
+ * greater than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static void checkPositionIndex(int index, int size, String desc) {
+ checkArgument(size >= 0, "negative size: %s", size);
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(format(
+ "%s (%s) must not be negative", desc, index));
+ }
+ if (index > size) {
+ throw new IndexOutOfBoundsException(format(
+ "%s (%s) must not be greater than size (%s)", desc, index, size));
+ }
+ }
+
+ /**
+ * Ensures that {@code start} and {@code end} specify a valid <i>positions</i>
+ * in an array, list or string of size {@code size}, and are in order. A
+ * position index may range from zero to {@code size}, inclusive.
+ *
+ * @param start a user-supplied index identifying a starting position in an
+ * array, list or string
+ * @param end a user-supplied index identifying a ending position in an array,
+ * list or string
+ * @param size the size of that array, list or string
+ * @throws IndexOutOfBoundsException if either index is negative or is
+ * greater than {@code size}, or if {@code end} is less than {@code start}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ public static void checkPositionIndexes(int start, int end, int size) {
+ checkPositionIndex(start, size, "start index");
+ checkPositionIndex(end, size, "end index");
+ if (end < start) {
+ throw new IndexOutOfBoundsException(format(
+ "end index (%s) must not be less than start index (%s)", end, start));
+ }
+ }
+
+ /**
+ * Substitutes each {@code %s} in {@code template} with an argument. These
+ * are matched by position - the first {@code %s} gets {@code args[0]}, etc.
+ * If there are more arguments than placeholders, the unmatched arguments will
+ * be appended to the end of the formatted message in square braces.
+ *
+ * @param template a non-null string containing 0 or more {@code %s}
+ * placeholders.
+ * @param args the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}. Arguments can be null.
+ */
+ // VisibleForTesting
+ static String format(String template, Object... args) {
+ // start substituting the arguments into the '%s' placeholders
+ StringBuilder builder = new StringBuilder(
+ template.length() + 16 * args.length);
+ int templateStart = 0;
+ int i = 0;
+ while (i < args.length) {
+ int placeholderStart = template.indexOf("%s", templateStart);
+ if (placeholderStart == -1) {
+ break;
+ }
+ builder.append(template.substring(templateStart, placeholderStart));
+ builder.append(args[i++]);
+ templateStart = placeholderStart + 2;
+ }
+ builder.append(template.substring(templateStart));
+
+ // if we run out of placeholders, append the extra args in square braces
+ if (i < args.length) {
+ builder.append(" [");
+ builder.append(args[i++]);
+ while (i < args.length) {
+ builder.append(", ");
+ builder.append(args[i++]);
+ }
+ builder.append("]");
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/test/com/google/inject/internal/ReferenceCacheTest.java b/test/com/google/inject/internal/ReferenceCacheTest.java
deleted file mode 100644
index 191da13..0000000
--- a/test/com/google/inject/internal/ReferenceCacheTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import static com.google.inject.internal.ReferenceType.SOFT;
-import static com.google.inject.internal.ReferenceType.STRONG;
-import static com.google.inject.internal.ReferenceType.WEAK;
-import java.util.concurrent.CountDownLatch;
-import junit.framework.TestCase;
-
-/**
- * @author crazybob@google.com (Bob Lee)
- */
-public class ReferenceCacheTest extends TestCase {
-
- public void testRuntimeException() {
- class CreationException extends RuntimeException {}
- try {
- new ReferenceCache() {
- protected Object create(Object key) {
- throw new CreationException();
- }
- }.get(new Object());
- fail();
- } catch (CreationException e) { /* expected */ }
- }
-
- public void testApply() {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- WEAK, WEAK, new SomeFunction());
- assertEquals(Integer.valueOf(1), cache.get("foo"));
- assertEquals(Integer.valueOf(1), cache.get("foo"));
- assertEquals(Integer.valueOf(2), cache.get("bar"));
- assertEquals(Integer.valueOf(1), cache.get("foo"));
- assertEquals(Integer.valueOf(3), cache.get("baz"));
- }
-
- public void testSleepConcurrency() throws InterruptedException {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- WEAK, WEAK, new SleepFunction());
- assertConcurrency(cache, false);
- }
-
- public void testBusyConcurrency() throws InterruptedException {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- WEAK, WEAK, new BusyFunction());
- assertConcurrency(cache, false);
- }
-
- public void testFastConcurrency() throws InterruptedException {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- WEAK, WEAK, new SomeFunction());
- assertConcurrency(cache, false);
- }
-
- public void testSleepCanonical() throws InterruptedException {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- STRONG, SOFT, new SleepFunction());
- assertConcurrency(cache, true);
- }
-
- public void testBusyCanonical() throws InterruptedException {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- STRONG, SOFT, new BusyFunction());
- assertConcurrency(cache, true);
- }
-
- public void testFastCanonical() throws InterruptedException {
- ReferenceMap<String, Integer> cache = ReferenceCache.of(
- STRONG, SOFT, new SomeFunction());
- assertConcurrency(cache, true);
- }
-
- private static void assertConcurrency(
- final ReferenceMap<String, Integer> cache,
- final boolean simulateAliasing) throws InterruptedException {
- final int n = 20;
- final CountDownLatch startSignal = new CountDownLatch(1);
- final CountDownLatch doneSignal = new CountDownLatch(n);
- for (int i = 0; i < n; i++) {
- new Thread() {
- public void run() {
- try {
- startSignal.await();
- for (int j = 0; j < n; j++) {
- cache.get(simulateAliasing ? new String("foo") : "foo");
- }
- doneSignal.countDown();
- } catch (InterruptedException ignored) {}
- }
- }.start();
- }
-
- startSignal.countDown();
- doneSignal.await();
- assertEquals(Integer.valueOf(1), cache.get("foo"));
- assertEquals(Integer.valueOf(2), cache.get("bar"));
- }
-
- private static class SomeFunction implements Function<String, Integer> {
- private int numApplies = 0;
- public Integer apply(String s) {
- return ++numApplies;
- }
- }
-
- private static class SleepFunction implements Function<String, Integer> {
- private int numApplies = 0;
- public Integer apply(String s) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- return ++numApplies;
- }
- }
-
- private static class BusyFunction implements Function<String, Integer> {
- private int numApplies = 0;
- public Integer apply(String s) {
- for (int i = 0; i < 1000; i++) {
- Math.sqrt(i);
- }
- return ++numApplies;
- }
- }
-}
diff --git a/test/com/google/inject/internal/ReferenceMapTest.java b/test/com/google/inject/internal/ReferenceMapTest.java
deleted file mode 100644
index 4a4c018..0000000
--- a/test/com/google/inject/internal/ReferenceMapTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import static com.google.inject.internal.ReferenceType.SOFT;
-import static com.google.inject.internal.ReferenceType.STRONG;
-import static com.google.inject.internal.ReferenceType.WEAK;
-import java.lang.ref.Reference;
-import java.util.Iterator;
-import java.util.concurrent.ConcurrentMap;
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link ReferenceMap}.
- *
- * @author crazybob@google.com (Bob Lee)
- * @author mbostock@google.com (Mike Bostock)
- */
-@SuppressWarnings({"unchecked"})
-public class ReferenceMapTest extends TestCase {
-
- private enum CleanupMode {
- ENQUEUE_KEY, ENQUEUE_VALUE, GC;
- }
-
- public void testValueCleanupWithWeakKey() {
- ReferenceMap map = new ReferenceMap(WEAK, STRONG);
- map.put(new Object(), new Object());
- assertCleanup(map, CleanupMode.GC);
- }
-
- public void testKeyCleanupWithWeakValue() {
- ReferenceMap map = new ReferenceMap(STRONG, WEAK);
- map.put(new Object(), new Object());
- assertCleanup(map, CleanupMode.GC);
- }
-
- public void testInternedValueCleanupWithWeakKey() {
- ReferenceMap map = new ReferenceMap(WEAK, STRONG);
- map.put(5, "foo");
- assertCleanup(map, CleanupMode.ENQUEUE_KEY);
- }
-
- public void testInternedValueCleanupWithSoftKey() {
- ReferenceMap map = new ReferenceMap(SOFT, STRONG);
- map.put(5, "foo");
- assertCleanup(map, CleanupMode.ENQUEUE_KEY);
- }
-
- public void testInternedKeyCleanupWithWeakValue() {
- ReferenceMap map = new ReferenceMap(STRONG, WEAK);
- map.put(5, "foo");
- assertCleanup(map, CleanupMode.ENQUEUE_VALUE);
- }
-
- public void testInternedKeyCleanupWithSoftValue() {
- ReferenceMap map = new ReferenceMap(STRONG, SOFT);
- map.put(5, "foo");
- assertCleanup(map, CleanupMode.ENQUEUE_VALUE);
- }
-
- private static void assertCleanup(ReferenceMap<?, ?> map,
- CleanupMode mode) {
- assertEquals(1, map.delegate.size());
-
- switch (mode) {
- case ENQUEUE_KEY: {
- ConcurrentMap delegate = map.delegate;
- Iterator keyIterator = delegate.keySet().iterator();
- Reference reference = ((Reference) keyIterator.next());
- reference.enqueue();
- break;
- }
- case ENQUEUE_VALUE: {
- ConcurrentMap delegate = map.delegate;
- Iterator valueIterator = delegate.values().iterator();
- Reference reference = ((Reference) valueIterator.next());
- reference.enqueue();
- break;
- }
- }
-
- // wait up to 5s
- for (int i = 0; i < 500; i++) {
- if (mode == CleanupMode.GC) {
- System.gc();
- }
- if (map.size() == 0) {
- return;
- }
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) { /* ignore */ }
- }
- fail();
- }
-}
diff --git a/test/com/google/inject/internal/ReferenceMapTestSuite.java b/test/com/google/inject/internal/ReferenceMapTestSuite.java
deleted file mode 100644
index 71f66d4..0000000
--- a/test/com/google/inject/internal/ReferenceMapTestSuite.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * @author crazybob@google.com (Bob Lee)
- */
-@SuppressWarnings({"unchecked"})
-public class ReferenceMapTestSuite {
-
- public static Test suite() {
- TestSuite suite = new TestSuite();
-
- Set<ReferenceType> referenceTypes =
- new HashSet<ReferenceType>();
- referenceTypes.addAll(Arrays.asList(ReferenceType.values()));
- referenceTypes.remove(ReferenceType.PHANTOM);
-
- // create test cases for each key and value type.
- for (Method method : MapTest.class.getMethods()) {
- String name = method.getName();
- if (name.startsWith("test")) {
- for (ReferenceType keyType : referenceTypes) {
- for (ReferenceType valueType : referenceTypes) {
- suite.addTest(new MapTest(name, keyType, valueType));
- }
- }
- }
- }
-
- return suite;
- }
-
- public static class MapTest extends TestCase {
-
- final ReferenceType keyType;
- final ReferenceType valueType;
-
- public MapTest(String name, ReferenceType keyType,
- ReferenceType valueType) {
- super(name);
- this.keyType = keyType;
- this.valueType = valueType;
- }
-
- public String getName() {
- return super.getName()
- + "For"
- + Strings.capitalize(keyType.toString().toLowerCase())
- + Strings.capitalize(valueType.toString().toLowerCase());
- }
-
- ReferenceMap newInstance() {
- return new ReferenceMap(keyType, valueType);
- }
-
- public void testContainsKey() {
- ReferenceMap map = newInstance();
- Object k = "key";
- map.put(k, "value");
- assertTrue(map.containsKey(k));
- }
-
- public void testClear() {
- ReferenceMap map = newInstance();
- String k = "key";
- map.put(k, "value");
- assertFalse(map.isEmpty());
- map.clear();
- assertTrue(map.isEmpty());
- assertNull(map.get(k));
- }
-
- public void testKeySet() {
- ReferenceMap map = newInstance();
- map.put("a", "foo");
- map.put("b", "foo");
- Set expected = new HashSet(Arrays.asList("a", "b"));
- assertEquals(expected, map.keySet());
- }
-
- public void testValues() {
- ReferenceMap map = newInstance();
- map.put("a", "1");
- map.put("b", "2");
- Set expected = new HashSet(Arrays.asList("1", "2"));
- Set actual = new HashSet();
- actual.addAll(map.values());
- assertEquals(expected, actual);
- }
-
- public void testPutIfAbsent() {
- ReferenceMap map = newInstance();
- map.putIfAbsent("a", "1");
- assertEquals("1", map.get("a"));
- map.putIfAbsent("a", "2");
- assertEquals("1", map.get("a"));
- }
-
- public void testReplace() {
- ReferenceMap map = newInstance();
- map.put("a", "1");
- map.replace("a", "2", "2");
- assertEquals("1", map.get("a"));
- map.replace("a", "1", "2");
- assertEquals("2", map.get("a"));
- }
-
- public void testContainsValue() {
- ReferenceMap map = newInstance();
- Object v = "value";
- map.put("key", v);
- assertTrue(map.containsValue(v));
- }
-
- public void testEntrySet() {
- final ReferenceMap map = newInstance();
- map.put("a", "1");
- map.put("b", "2");
- Set expected = new HashSet(Arrays.asList(
- map.new Entry("a", "1"),
- map.new Entry("b", "2")
- ));
- assertEquals(expected, map.entrySet());
- }
-
- public void testPutAll() {
- ReferenceMap map = newInstance();
- Object k = "key";
- Object v = "value";
- map.putAll(Collections.singletonMap(k, v));
- assertSame(v, map.get(k));
- }
-
- public void testRemove() {
- ReferenceMap map = newInstance();
- Object k = "key";
- map.put(k, "value");
- map.remove(k);
- assertFalse(map.containsKey(k));
- }
-
- public void testPutGet() {
- final Object k = new Object();
- final Object v = new Object();
- ReferenceMap map = newInstance();
- map.put(k, v);
- assertEquals(1, map.size());
- assertSame(v, map.get(k));
- assertEquals(1, map.size());
- assertNull(map.get(new Object()));
- }
-
- @SuppressWarnings("unchecked")
- public void testCreate() {
- final Object k = new Object();
- final Object v = new Object();
- ReferenceMap map = new ReferenceCache(
- keyType, valueType) {
- @Override protected Object create(Object key) {
- return key == k ? v : null;
- }
- };
- assertEquals(0, map.size());
- assertSame(v, map.get(k));
- assertSame(v, map.get(k));
- assertEquals(1, map.size());
-
- try {
- // create can't return null.
- map.get(new Object());
- fail();
- } catch (NullPointerException e) {}
- }
-
- public void testReferenceMapSerialization() throws IOException,
- ClassNotFoundException {
- Map map = newInstance();
- map.put(Key.FOO, Value.FOO);
- map = (Map) serializeAndDeserialize(map);
- map.put(Key.BAR, Value.BAR);
- assertSame(Value.FOO, map.get(Key.FOO));
- assertSame(Value.BAR, map.get(Key.BAR));
- assertNull(map.get(Key.TEE));
- }
-
- static class MockReferenceCache extends ReferenceCache {
-
- int count;
-
- public MockReferenceCache(ReferenceType keyReferenceType,
- ReferenceType valueReferenceType) {
- super(keyReferenceType, valueReferenceType);
- }
-
- protected Object create(Object key) {
- count++;
- return Value.valueOf(((Key) key).toString());
- }
- }
-
- public void testReferenceCacheSerialization() throws IOException,
- ClassNotFoundException {
- MockReferenceCache map = new MockReferenceCache(keyType, valueType);
- assertSame(Value.FOO, map.get(Key.FOO));
- assertSame(Value.BAR, map.get(Key.BAR));
- map = (MockReferenceCache) serializeAndDeserialize(map);
- assertSame(Value.FOO, map.get(Key.FOO));
- assertSame(Value.BAR, map.get(Key.BAR));
- assertSame(Value.TEE, map.get(Key.TEE));
- assertEquals(3, map.count);
- }
-
- @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- public Object serializeAndDeserialize(Object o) throws IOException,
- ClassNotFoundException {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- new ObjectOutputStream(out).writeObject(o);
- return new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()))
- .readObject();
- }
- }
-
- /**
- * Enums conveniently maintain instance identity across serialization.
- */
- enum Key {
- FOO, BAR, TEE;
- }
-
- enum Value {
- FOO, BAR, TEE;
- }
-}
\ No newline at end of file