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&lt;$T$&gt; $var$ = new java.util.ArrayList&lt;$T$&gt;();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List&lt;$T$&gt; $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&lt;$T$&gt; $var$ = new java.util.ArrayList&lt;$T$&gt;($iterable$);" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List&lt;$T$&gt; $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&lt;$T$&gt; $var$ = new java.util.LinkedList&lt;$T$&gt;();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List&lt;$T$&gt; $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&lt;$T$&gt; $var$ = new java.util.LinkedList&lt;$T$&gt;($iterable$);" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.List&lt;$T$&gt; $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&lt;$T$&gt; $var$ = new java.util.HashSet&lt;$T$&gt;();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.Set&lt;$T$&gt; $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&lt;$T$&gt; $var$ = new HashSet&lt;$T$&gt;($iterable$);" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.Set&lt;$T$&gt; $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&lt;$K$, $V$&gt; $var$ = new java.util.HashMap&lt;$K$, $V$&gt;();" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="java.util.Map&lt;$K$, $V$&gt; $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&lt;K, V&gt; instead of Map&lt;K, List&lt;V&gt;&gt;" text="java.util.Map&lt;$K$, java.util.List&lt;$V$&gt;&gt; $var$ = $expr$;" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Multimap&lt;$K$, $V$&gt; $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&lt;K, V&gt; instead of Map&lt;K, Set&lt;V&gt;&gt;" text="java.util.Map&lt;$K$, java.util.Set&lt;$V$&gt;&gt; $var$ = $expr$;" recursive="false" caseInsensitive="true" reformatAccordingToStyle="true" shortenFQN="true" replacement="com.google.common.collect.Multimap&lt;$K$, $V$&gt; $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$) {&#10;  throw new IllegalStateException($message$);&#10;}" 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$) {&#10;  throw new IllegalArgumentException($message$);&#10;}" 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) {&#10;  throw new NullPointerException($message$);&#10;}" 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$) &gt; 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 &gt;=" text="$a$.compareTo($b$) &gt;= 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$) &lt; 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 &lt;=" text="$a$.compareTo($b$) &lt;= 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$) &gt; 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, &gt;=)" text="$a$.compareTo($b$) &gt;= 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$) &lt; 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, &lt;=)" text="$a$.compareTo($b$) &lt;= 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$) {&#10;  throw new IllegalStateException();&#10;}" 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$) {&#10;  throw new IllegalArgumentException();&#10;}" 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) {&#10;  throw new NullPointerException();&#10;}" 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&lt;String,Integer&gt;</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&lt;String,Integer&gt;</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&lt;Float,Float&gt;</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