blob: 515607ff3449f62ea270f13c396b53a788a468ed [file] [log] [blame]
crazybobleec49e0912007-08-31 23:52:25 +00001/*
2 * Copyright (C) 2007 Google Inc.
crazyboblee66b415a2006-08-25 02:01:19 +00003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
kevinb9ncad2c2b2007-05-15 17:28:03 +000017package com.google.inject.internal;
crazyboblee66b415a2006-08-25 02:01:19 +000018
kevinb9ncad2c2b2007-05-15 17:28:03 +000019import static com.google.inject.internal.ReferenceType.STRONG;
crazybobleec49e0912007-08-31 23:52:25 +000020
crazyboblee66b415a2006-08-25 02:01:19 +000021import java.io.IOException;
22import java.io.ObjectInputStream;
23import java.io.ObjectOutputStream;
24import java.io.Serializable;
25import java.lang.ref.Reference;
crazybobleec49e0912007-08-31 23:52:25 +000026import java.util.AbstractMap;
27import java.util.AbstractSet;
28import java.util.Iterator;
crazyboblee66b415a2006-08-25 02:01:19 +000029import java.util.Map;
crazybobleec49e0912007-08-31 23:52:25 +000030import java.util.NoSuchElementException;
crazyboblee66b415a2006-08-25 02:01:19 +000031import java.util.Set;
32import java.util.concurrent.ConcurrentHashMap;
33import java.util.concurrent.ConcurrentMap;
34
35/**
crazybobleec49e0912007-08-31 23:52:25 +000036 * Concurrent hash map that wraps keys and/or values in soft or weak references.
37 * Does not support null keys or values. Uses identity equality for weak and
38 * soft keys.
crazyboblee66b415a2006-08-25 02:01:19 +000039 *
40 * <p>The concurrent semantics of {@link ConcurrentHashMap} combined with the
41 * fact that the garbage collector can asynchronously reclaim and clean up
crazybobleec49e0912007-08-31 23:52:25 +000042 * keys and values at any time can lead to some racy semantics. For example,
43 * {@link #size()} returns an upper bound on the size; that is, the actual size
44 * may be smaller in cases where the key or value has been reclaimed but the map
45 * entry has not been cleaned up yet.
crazyboblee66b415a2006-08-25 02:01:19 +000046 *
47 * @author crazybob@google.com (Bob Lee)
crazybobleec49e0912007-08-31 23:52:25 +000048 * @author fry@google.com (Charles Fry)
crazyboblee66b415a2006-08-25 02:01:19 +000049 */
50@SuppressWarnings("unchecked")
crazybobleec49e0912007-08-31 23:52:25 +000051public class ReferenceMap<K, V> extends AbstractMap<K, V>
52 implements ConcurrentMap<K, V>, Serializable {
crazyboblee66b415a2006-08-25 02:01:19 +000053
54 transient ConcurrentMap<Object, Object> delegate;
55
crazybobleec49e0912007-08-31 23:52:25 +000056 private final ReferenceType keyReferenceType;
57 private final ReferenceType valueReferenceType;
crazyboblee66b415a2006-08-25 02:01:19 +000058
59 /**
60 * Concurrent hash map that wraps keys and/or values based on specified
61 * reference types.
62 *
63 * @param keyReferenceType key reference type
64 * @param valueReferenceType value reference type
65 */
crazybobleec49e0912007-08-31 23:52:25 +000066 public ReferenceMap(
67 ReferenceType keyReferenceType, ReferenceType valueReferenceType) {
crazyboblee66b415a2006-08-25 02:01:19 +000068 ensureNotNull(keyReferenceType, valueReferenceType);
69
70 if (keyReferenceType == ReferenceType.PHANTOM
71 || valueReferenceType == ReferenceType.PHANTOM) {
72 throw new IllegalArgumentException("Phantom references not supported.");
73 }
74
75 this.delegate = new ConcurrentHashMap<Object, Object>();
76 this.keyReferenceType = keyReferenceType;
77 this.valueReferenceType = valueReferenceType;
78 }
79
80 V internalGet(K key) {
81 Object valueReference = delegate.get(makeKeyReferenceAware(key));
crazybobleec49e0912007-08-31 23:52:25 +000082 return dereferenceValue(valueReference);
crazyboblee66b415a2006-08-25 02:01:19 +000083 }
84
85 public V get(final Object key) {
86 ensureNotNull(key);
87 return internalGet((K) key);
88 }
89
crazybobleec49e0912007-08-31 23:52:25 +000090 private V execute(Strategy strategy, K key, V value) {
crazyboblee66b415a2006-08-25 02:01:19 +000091 ensureNotNull(key, value);
92 Object keyReference = referenceKey(key);
crazybobleec49e0912007-08-31 23:52:25 +000093 return (V) strategy.execute(
94 this, keyReference, referenceValue(keyReference, value)
crazyboblee66b415a2006-08-25 02:01:19 +000095 );
crazyboblee66b415a2006-08-25 02:01:19 +000096 }
97
98 public V put(K key, V value) {
99 return execute(putStrategy(), key, value);
100 }
101
102 public V remove(Object key) {
103 ensureNotNull(key);
104 Object referenceAwareKey = makeKeyReferenceAware(key);
105 Object valueReference = delegate.remove(referenceAwareKey);
crazybobleec49e0912007-08-31 23:52:25 +0000106 return dereferenceValue(valueReference);
crazyboblee66b415a2006-08-25 02:01:19 +0000107 }
108
109 public int size() {
110 return delegate.size();
111 }
112
113 public boolean isEmpty() {
114 return delegate.isEmpty();
115 }
116
117 public boolean containsKey(Object key) {
118 ensureNotNull(key);
119 Object referenceAwareKey = makeKeyReferenceAware(key);
120 return delegate.containsKey(referenceAwareKey);
121 }
122
123 public boolean containsValue(Object value) {
124 ensureNotNull(value);
125 for (Object valueReference : delegate.values()) {
126 if (value.equals(dereferenceValue(valueReference))) {
127 return true;
128 }
129 }
130 return false;
131 }
132
133 public void putAll(Map<? extends K, ? extends V> t) {
134 for (Map.Entry<? extends K, ? extends V> entry : t.entrySet()) {
135 put(entry.getKey(), entry.getValue());
136 }
137 }
138
139 public void clear() {
140 delegate.clear();
141 }
142
crazyboblee66b415a2006-08-25 02:01:19 +0000143 public V putIfAbsent(K key, V value) {
crazyboblee66b415a2006-08-25 02:01:19 +0000144 return execute(putIfAbsentStrategy(), key, value);
145 }
146
147 public boolean remove(Object key, Object value) {
148 ensureNotNull(key, value);
149 Object referenceAwareKey = makeKeyReferenceAware(key);
150 Object referenceAwareValue = makeValueReferenceAware(value);
151 return delegate.remove(referenceAwareKey, referenceAwareValue);
152 }
153
154 public boolean replace(K key, V oldValue, V newValue) {
155 ensureNotNull(key, oldValue, newValue);
156 Object keyReference = referenceKey(key);
157
158 Object referenceAwareOldValue = makeValueReferenceAware(oldValue);
crazybobleec49e0912007-08-31 23:52:25 +0000159 return delegate.replace(keyReference, referenceAwareOldValue,
160 referenceValue(keyReference, newValue)
crazyboblee66b415a2006-08-25 02:01:19 +0000161 );
162 }
163
164 public V replace(K key, V value) {
crazyboblee66b415a2006-08-25 02:01:19 +0000165 return execute(replaceStrategy(), key, value);
166 }
167
crazybobleec49e0912007-08-31 23:52:25 +0000168 private transient volatile Set<Map.Entry<K, V>> entrySet = null;
169
crazyboblee66b415a2006-08-25 02:01:19 +0000170 public Set<Map.Entry<K, V>> entrySet() {
crazybobleec49e0912007-08-31 23:52:25 +0000171 if (entrySet == null) {
172 entrySet = new EntrySet();
crazyboblee66b415a2006-08-25 02:01:19 +0000173 }
crazybobleec49e0912007-08-31 23:52:25 +0000174 return entrySet;
crazyboblee66b415a2006-08-25 02:01:19 +0000175 }
176
crazybobleec49e0912007-08-31 23:52:25 +0000177 /** Dereferences an entry. Returns null if the key or value has been gc'ed. */
178 private Entry dereferenceEntry(Map.Entry<Object, Object> entry) {
179 K key = dereferenceKey(entry.getKey());
crazyboblee66b415a2006-08-25 02:01:19 +0000180 V value = dereferenceValue(entry.getValue());
181 return (key == null || value == null)
182 ? null
183 : new Entry(key, value);
184 }
185
crazybobleec49e0912007-08-31 23:52:25 +0000186 /** Creates a reference for a key. */
crazyboblee66b415a2006-08-25 02:01:19 +0000187 Object referenceKey(K key) {
188 switch (keyReferenceType) {
crazybobleec49e0912007-08-31 23:52:25 +0000189 case STRONG:
190 return key;
191 case SOFT:
192 return new SoftKeyReference(key);
193 case WEAK:
194 return new WeakKeyReference(key);
195 default:
196 throw new AssertionError();
crazyboblee66b415a2006-08-25 02:01:19 +0000197 }
198 }
199
crazybobleec49e0912007-08-31 23:52:25 +0000200 /** Converts a reference to a key. */
201 private K dereferenceKey(Object o) {
crazyboblee66b415a2006-08-25 02:01:19 +0000202 return (K) dereference(keyReferenceType, o);
203 }
204
crazybobleec49e0912007-08-31 23:52:25 +0000205 /** Converts a reference to a value. */
crazyboblee66b415a2006-08-25 02:01:19 +0000206 V dereferenceValue(Object o) {
crazybobleec49e0912007-08-31 23:52:25 +0000207 if (o == null) {
208 return null;
209 }
210 Object value = dereference(valueReferenceType, o);
211 if (o instanceof InternalReference) {
212 InternalReference ref = (InternalReference) o;
213 if (value == null) {
214 // old value was garbage collected
215 ref.finalizeReferent();
216 }
217 }
218 return (V) value;
crazyboblee66b415a2006-08-25 02:01:19 +0000219 }
220
crazybobleec49e0912007-08-31 23:52:25 +0000221 /** Returns the refererent for reference given its reference type. */
222 private Object dereference(ReferenceType referenceType, Object reference) {
crazyboblee66b415a2006-08-25 02:01:19 +0000223 return referenceType == STRONG ? reference : ((Reference) reference).get();
224 }
225
crazybobleec49e0912007-08-31 23:52:25 +0000226 /** Creates a reference for a value. */
crazyboblee66b415a2006-08-25 02:01:19 +0000227 Object referenceValue(Object keyReference, Object value) {
228 switch (valueReferenceType) {
crazybobleec49e0912007-08-31 23:52:25 +0000229 case STRONG:
230 return value;
231 case SOFT:
232 return new SoftValueReference(keyReference, value);
233 case WEAK:
234 return new WeakValueReference(keyReference, value);
235 default:
236 throw new AssertionError();
crazyboblee66b415a2006-08-25 02:01:19 +0000237 }
238 }
239
240 /**
crazyboblee66b415a2006-08-25 02:01:19 +0000241 * Wraps key so it can be compared to a referenced key for equality.
242 */
crazybobleec49e0912007-08-31 23:52:25 +0000243 private Object makeKeyReferenceAware(Object o) {
crazyboblee66b415a2006-08-25 02:01:19 +0000244 return keyReferenceType == STRONG ? o : new KeyReferenceAwareWrapper(o);
245 }
246
crazybobleec49e0912007-08-31 23:52:25 +0000247 /** Wraps value so it can be compared to a referenced value for equality. */
248 private Object makeValueReferenceAware(Object o) {
crazyboblee66b415a2006-08-25 02:01:19 +0000249 return valueReferenceType == STRONG ? o : new ReferenceAwareWrapper(o);
250 }
251
252 /**
crazybobleec49e0912007-08-31 23:52:25 +0000253 * Marker interface to differentiate external and internal references. Also
254 * duplicates FinalizableReference and Reference.get for internal use.
crazyboblee66b415a2006-08-25 02:01:19 +0000255 */
crazybobleec49e0912007-08-31 23:52:25 +0000256 interface InternalReference {
257 void finalizeReferent();
258
259 Object get();
crazyboblee66b415a2006-08-25 02:01:19 +0000260 }
261
crazybobleec49e0912007-08-31 23:52:25 +0000262 private static int keyHashCode(Object key) {
crazyboblee66b415a2006-08-25 02:01:19 +0000263 return System.identityHashCode(key);
264 }
265
crazybobleec49e0912007-08-31 23:52:25 +0000266 /*
crazyboblee66b415a2006-08-25 02:01:19 +0000267 * Tests weak and soft references for identity equality. Compares references
crazybobleec49e0912007-08-31 23:52:25 +0000268 * to other references and wrappers. If o is a reference, this returns true if
269 * r == o or if r and o reference the same non-null object. If o is a wrapper,
270 * this returns true if r's referent is identical to the wrapped object.
crazyboblee66b415a2006-08-25 02:01:19 +0000271 */
crazybobleec49e0912007-08-31 23:52:25 +0000272 private static boolean referenceEquals(Reference r, Object o) {
crazyboblee66b415a2006-08-25 02:01:19 +0000273 // compare reference to reference.
274 if (o instanceof InternalReference) {
275 // are they the same reference? used in cleanup.
276 if (o == r) {
277 return true;
278 }
279
280 // do they reference identical values? used in conditional puts.
281 Object referent = ((Reference) o).get();
282 return referent != null && referent == r.get();
283 }
284
285 // is the wrapped object identical to the referent? used in lookups.
286 return ((ReferenceAwareWrapper) o).unwrap() == r.get();
287 }
288
289 /**
crazybobleec49e0912007-08-31 23:52:25 +0000290 * Returns {@code true} if the specified value reference has been garbage
291 * collected. The value behind the reference is also passed in, rather than
292 * queried inside this method, to ensure that the return statement of this
293 * method will still hold true after it has returned (that is, a value
294 * reference exists outside of this method which will prevent that value from
295 * being garbage collected).
296 *
297 * @param valueReference the value reference to be tested
298 * @param value the object referenced by {@code valueReference}
299 * @return {@code true} if {@code valueReference} is non-null and {@code
300 * value} is null
301 */
302 private static boolean isExpired(Object valueReference, Object value) {
303 return (valueReference != null) && (value == null);
304 }
305
306 /**
crazyboblee66b415a2006-08-25 02:01:19 +0000307 * Big hack. Used to compare keys and values to referenced keys and values
308 * without creating more references.
309 */
310 static class ReferenceAwareWrapper {
kevinb9n6a565c72007-02-11 01:58:33 +0000311 final Object wrapped;
crazyboblee66b415a2006-08-25 02:01:19 +0000312
313 ReferenceAwareWrapper(Object wrapped) {
314 this.wrapped = wrapped;
315 }
316
317 Object unwrap() {
318 return wrapped;
319 }
320
321 public int hashCode() {
322 return wrapped.hashCode();
323 }
324
325 public boolean equals(Object obj) {
326 // defer to reference's equals() logic.
327 return obj.equals(this);
328 }
329 }
330
crazybobleec49e0912007-08-31 23:52:25 +0000331 /** Used for keys. Overrides hash code to use identity hash code. */
crazyboblee66b415a2006-08-25 02:01:19 +0000332 static class KeyReferenceAwareWrapper extends ReferenceAwareWrapper {
crazyboblee66b415a2006-08-25 02:01:19 +0000333 public KeyReferenceAwareWrapper(Object wrapped) {
334 super(wrapped);
335 }
336
337 public int hashCode() {
338 return System.identityHashCode(wrapped);
339 }
340 }
341
342 class SoftKeyReference extends FinalizableSoftReference<Object>
343 implements InternalReference {
kevinb9n6a565c72007-02-11 01:58:33 +0000344 final int hashCode;
crazyboblee66b415a2006-08-25 02:01:19 +0000345
346 public SoftKeyReference(Object key) {
347 super(key);
348 this.hashCode = keyHashCode(key);
349 }
350
351 public void finalizeReferent() {
352 delegate.remove(this);
353 }
354
355 @Override public int hashCode() {
356 return this.hashCode;
357 }
358
359 @Override public boolean equals(Object o) {
360 return referenceEquals(this, o);
361 }
362 }
363
364 class WeakKeyReference extends FinalizableWeakReference<Object>
365 implements InternalReference {
kevinb9n6a565c72007-02-11 01:58:33 +0000366 final int hashCode;
crazyboblee66b415a2006-08-25 02:01:19 +0000367
368 public WeakKeyReference(Object key) {
369 super(key);
370 this.hashCode = keyHashCode(key);
371 }
372
373 public void finalizeReferent() {
374 delegate.remove(this);
375 }
376
377 @Override public int hashCode() {
378 return this.hashCode;
379 }
380
381 @Override public boolean equals(Object o) {
382 return referenceEquals(this, o);
383 }
384 }
385
386 class SoftValueReference extends FinalizableSoftReference<Object>
387 implements InternalReference {
kevinb9n6a565c72007-02-11 01:58:33 +0000388 final Object keyReference;
crazyboblee66b415a2006-08-25 02:01:19 +0000389
390 public SoftValueReference(Object keyReference, Object value) {
391 super(value);
392 this.keyReference = keyReference;
393 }
394
395 public void finalizeReferent() {
396 delegate.remove(keyReference, this);
397 }
398
399 @Override public boolean equals(Object obj) {
400 return referenceEquals(this, obj);
401 }
402 }
403
404 class WeakValueReference extends FinalizableWeakReference<Object>
405 implements InternalReference {
kevinb9n6a565c72007-02-11 01:58:33 +0000406 final Object keyReference;
crazyboblee66b415a2006-08-25 02:01:19 +0000407
408 public WeakValueReference(Object keyReference, Object value) {
409 super(value);
410 this.keyReference = keyReference;
411 }
412
413 public void finalizeReferent() {
414 delegate.remove(keyReference, this);
415 }
416
417 @Override public boolean equals(Object obj) {
418 return referenceEquals(this, obj);
419 }
420 }
421
422 protected interface Strategy {
crazybobleec49e0912007-08-31 23:52:25 +0000423 public Object execute(
424 ReferenceMap map, Object keyReference, Object valueReference);
crazyboblee66b415a2006-08-25 02:01:19 +0000425 }
426
crazybobleec49e0912007-08-31 23:52:25 +0000427 // TODO(crazybob): a getter called put() is probably a bad idea
crazyboblee66b415a2006-08-25 02:01:19 +0000428 protected Strategy putStrategy() {
429 return PutStrategy.PUT;
430 }
431
432 protected Strategy putIfAbsentStrategy() {
433 return PutStrategy.PUT_IF_ABSENT;
434 }
435
436 protected Strategy replaceStrategy() {
437 return PutStrategy.REPLACE;
438 }
439
crazybobleec49e0912007-08-31 23:52:25 +0000440 private enum PutStrategy implements Strategy {
crazyboblee66b415a2006-08-25 02:01:19 +0000441 PUT {
crazybobleec49e0912007-08-31 23:52:25 +0000442 public Object execute(
443 ReferenceMap map, Object keyReference, Object valueReference) {
444 return map.dereferenceValue(
445 map.delegate.put(keyReference, valueReference));
crazyboblee66b415a2006-08-25 02:01:19 +0000446 }
447 },
448
449 REPLACE {
crazybobleec49e0912007-08-31 23:52:25 +0000450 public Object execute(
451 ReferenceMap map, Object keyReference, Object valueReference) {
452 // ensure that the existing value is not collected
453 do {
454 Object existingValueReference;
455 Object existingValue;
456 do {
457 existingValueReference = map.delegate.get(keyReference);
458 existingValue = map.dereferenceValue(existingValueReference);
459 } while (isExpired(existingValueReference, existingValue));
460
461 if (existingValueReference == null) {
462 // nothing to replace
463 return false;
464 }
465
466 if (map.delegate.replace(
467 keyReference, existingValueReference, valueReference)) {
468 // existingValue didn't expire since we still have a reference to it
469 return existingValue;
470 }
471 } while (true);
crazyboblee66b415a2006-08-25 02:01:19 +0000472 }
473 },
474
475 PUT_IF_ABSENT {
crazybobleec49e0912007-08-31 23:52:25 +0000476 public Object execute(
477 ReferenceMap map, Object keyReference, Object valueReference) {
478 Object existingValueReference;
479 Object existingValue;
480 do {
481 existingValueReference
482 = map.delegate.putIfAbsent(keyReference, valueReference);
483 existingValue = map.dereferenceValue(existingValueReference);
484 } while (isExpired(existingValueReference, existingValue));
485 return existingValue;
crazyboblee66b415a2006-08-25 02:01:19 +0000486 }
crazybobleec49e0912007-08-31 23:52:25 +0000487 },
488 }
crazyboblee66b415a2006-08-25 02:01:19 +0000489
490 private static PutStrategy defaultPutStrategy;
491
492 protected PutStrategy getPutStrategy() {
493 return defaultPutStrategy;
494 }
495
crazyboblee66b415a2006-08-25 02:01:19 +0000496 class Entry implements Map.Entry<K, V> {
kevinb9n6a565c72007-02-11 01:58:33 +0000497 final K key;
crazybobleec49e0912007-08-31 23:52:25 +0000498 V value;
crazyboblee66b415a2006-08-25 02:01:19 +0000499
500 public Entry(K key, V value) {
501 this.key = key;
502 this.value = value;
503 }
504
505 public K getKey() {
506 return this.key;
507 }
508
509 public V getValue() {
510 return this.value;
511 }
512
crazybobleec49e0912007-08-31 23:52:25 +0000513 public V setValue(V newValue) {
514 value = newValue;
515 return put(key, newValue);
crazyboblee66b415a2006-08-25 02:01:19 +0000516 }
517
518 public int hashCode() {
519 return key.hashCode() * 31 + value.hashCode();
520 }
521
522 public boolean equals(Object o) {
523 if (!(o instanceof ReferenceMap.Entry)) {
524 return false;
525 }
526
527 Entry entry = (Entry) o;
528 return key.equals(entry.key) && value.equals(entry.value);
529 }
530
531 public String toString() {
532 return key + "=" + value;
533 }
534 }
535
crazybobleec49e0912007-08-31 23:52:25 +0000536 private class ReferenceIterator implements Iterator<Map.Entry<K, V>> {
537 private Iterator<Map.Entry<Object, Object>> i =
538 delegate.entrySet().iterator();
539 private Map.Entry<K, V> nextEntry;
540 private Map.Entry<K, V> lastReturned;
541
542 public ReferenceIterator() {
543 advance();
544 }
545
546 private void advance() {
547 while (i.hasNext()) {
548 Map.Entry<K, V> entry = dereferenceEntry(i.next());
549 if (entry != null) {
550 nextEntry = entry;
551 return;
552 }
553 }
554
555 // nothing left
556 nextEntry = null;
557 }
558
559 public boolean hasNext() {
560 return nextEntry != null;
561 }
562
563 public Map.Entry<K, V> next() {
564 if (nextEntry == null) {
565 throw new NoSuchElementException();
566 }
567 lastReturned = nextEntry;
568 advance();
569 return lastReturned;
570 }
571
572 public void remove() {
573 ReferenceMap.this.remove(lastReturned.getKey());
574 }
575 }
576
577 private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
578 public Iterator<Map.Entry<K, V>> iterator() {
579 return new ReferenceIterator();
580 }
581
582 public int size() {
583 return delegate.size();
584 }
585
586 public boolean contains(Object o) {
587 if (!(o instanceof Map.Entry)) {
588 return false;
589 }
590 Map.Entry<K, V> e = (Map.Entry<K, V>) o;
591 V v = ReferenceMap.this.get(e.getKey());
592 return v != null && v.equals(e.getValue());
593 }
594
595 public boolean remove(Object o) {
596 if (!(o instanceof Map.Entry)) {
597 return false;
598 }
599 Map.Entry<K, V> e = (Map.Entry<K, V>) o;
600 return ReferenceMap.this.remove(e.getKey(), e.getValue());
601 }
602
603 public void clear() {
604 delegate.clear();
605 }
606 };
607
608 // TODO(kevinb): use preconditions
crazyboblee66b415a2006-08-25 02:01:19 +0000609 static void ensureNotNull(Object o) {
610 if (o == null) {
611 throw new NullPointerException();
612 }
613 }
614
615 static void ensureNotNull(Object... array) {
616 for (int i = 0; i < array.length; i++) {
617 if (array[i] == null) {
618 throw new NullPointerException("Argument #" + i + " is null.");
619 }
620 }
621 }
622
crazybobleec49e0912007-08-31 23:52:25 +0000623 private void writeObject(ObjectOutputStream out) throws IOException {
crazyboblee66b415a2006-08-25 02:01:19 +0000624 out.defaultWriteObject();
625 out.writeInt(size());
626 for (Map.Entry<Object, Object> entry : delegate.entrySet()) {
627 Object key = dereferenceKey(entry.getKey());
628 Object value = dereferenceValue(entry.getValue());
629
630 // don't persist gc'ed entries.
631 if (key != null && value != null) {
632 out.writeObject(key);
633 out.writeObject(value);
634 }
635 }
636 out.writeObject(null);
637 }
638
crazybobleec49e0912007-08-31 23:52:25 +0000639 private void readObject(ObjectInputStream in)
640 throws IOException, ClassNotFoundException {
crazyboblee66b415a2006-08-25 02:01:19 +0000641 in.defaultReadObject();
642 int size = in.readInt();
643 this.delegate = new ConcurrentHashMap<Object, Object>(size);
644 while (true) {
645 K key = (K) in.readObject();
646 if (key == null) {
647 break;
648 }
649 V value = (V) in.readObject();
650 put(key, value);
651 }
652 }
653
crazybobleec49e0912007-08-31 23:52:25 +0000654 private static final long serialVersionUID = 0;
crazyboblee66b415a2006-08-25 02:01:19 +0000655}