1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
|
/*
* Copyright (C) 2009 The Guava Authors
*
* 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.common.collect;
import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Ascii;
import com.google.common.base.Equivalence;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Ticker;
import com.google.common.collect.ComputingConcurrentHashMap.ComputingMapAdapter;
import com.google.common.collect.MapMakerInternalMap.Strength;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* <p>A builder of {@link ConcurrentMap} instances having any combination of the following features:
*
* <ul>
* <li>keys or values automatically wrapped in {@linkplain WeakReference weak} or {@linkplain
* SoftReference soft} references
* <li>notification of evicted (or otherwise removed) entries
* <li>on-demand computation of values for keys not already present
* </ul>
*
* <p>Usage example: <pre> {@code
*
* ConcurrentMap<Key, Graph> graphs = new MapMaker()
* .concurrencyLevel(4)
* .weakKeys()
* .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 similarly to a {@link ConcurrentHashMap}.
*
* <p>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.
*
* <p><b>Note:</b> by default, the returned map uses equality comparisons (the {@link Object#equals
* 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 view collections of the returned map have <i>weakly consistent iterators</i>. This means
* that they are safe for concurrent use, but if other threads modify the map after the iterator is
* created, it is undefined which of these changes, if any, are reflected in that iterator. These
* iterators never throw {@link ConcurrentModificationException}.
*
* <p>If soft or weak references were requested, it is possible for a key or value present in the
* the map to be reclaimed by the garbage collector. If this happens, the entry automatically
* disappears from the map. A partially-reclaimed entry is never exposed to the user. Any {@link
* java.util.Map.Entry} instance retrieved from the map's {@linkplain Map#entrySet entry set} is a
* snapshot of that entry's state at the time of retrieval; such entries do, however, support {@link
* java.util.Map.Entry#setValue}, which simply calls {@link Map#put} on the entry's key.
*
* <p>The maps produced by {@code MapMaker} are serializable, and the deserialized maps retain all
* the configuration properties of the original map. During deserialization, if the original map had
* used soft or weak references, the entries are reconstructed as they were, but it's not unlikely
* they'll be quickly garbage-collected before they are ever accessed.
*
* <p>{@code new MapMaker().weakKeys().makeMap()} is a recommended replacement for {@link
* java.util.WeakHashMap}, but note that it compares keys using object identity whereas {@code
* WeakHashMap} uses {@link Object#equals}.
*
* @author Bob Lee
* @author Charles Fry
* @author Kevin Bourrillion
* @since 2.0 (imported from Google Collections Library)
*/
@GwtCompatible(emulated = true)
public final class MapMaker extends GenericMapMaker<Object, Object> {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int DEFAULT_CONCURRENCY_LEVEL = 4;
private static final int DEFAULT_EXPIRATION_NANOS = 0;
static final int UNSET_INT = -1;
// TODO(kevinb): dispense with this after benchmarking
boolean useCustomMap;
int initialCapacity = UNSET_INT;
int concurrencyLevel = UNSET_INT;
int maximumSize = UNSET_INT;
Strength keyStrength;
Strength valueStrength;
long expireAfterWriteNanos = UNSET_INT;
long expireAfterAccessNanos = UNSET_INT;
RemovalCause nullRemovalCause;
Equivalence<Object> keyEquivalence;
Ticker ticker;
/**
* Constructs a new {@code MapMaker} instance with default settings, including strong keys, strong
* values, and no automatic eviction of any kind.
*/
public MapMaker() {}
/**
* Sets a custom {@code Equivalence} strategy for comparing keys.
*
* <p>By default, the map uses {@link Equivalence#identity} to determine key equality when
* {@link #weakKeys} or {@link #softKeys} is specified, and {@link Equivalence#equals()}
* otherwise. The only place this is used is in {@link Interners.WeakInterner}.
*/
@GwtIncompatible("To be supported")
@Override
MapMaker keyEquivalence(Equivalence<Object> equivalence) {
checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence);
keyEquivalence = checkNotNull(equivalence);
this.useCustomMap = true;
return this;
}
Equivalence<Object> getKeyEquivalence() {
return firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence());
}
/**
* Sets the minimum total size for the internal hash tables. For example, if the initial capacity
* is {@code 60}, and the concurrency level is {@code 8}, then eight segments are created, each
* having a hash table of size eight. Providing a large enough estimate at construction time
* avoids the need for expensive resizing operations later, but setting this value unnecessarily
* high wastes memory.
*
* @throws IllegalArgumentException if {@code initialCapacity} is negative
* @throws IllegalStateException if an initial capacity was already set
*/
@Override
public MapMaker initialCapacity(int initialCapacity) {
checkState(this.initialCapacity == UNSET_INT, "initial capacity was already set to %s",
this.initialCapacity);
checkArgument(initialCapacity >= 0);
this.initialCapacity = initialCapacity;
return this;
}
int getInitialCapacity() {
return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity;
}
/**
* Specifies the maximum number of entries the map may contain. Note that the map <b>may evict an
* entry before this limit is exceeded</b>. As the map size grows close to the maximum, the map
* evicts entries that are less likely to be used again. For example, the map may evict an entry
* because it hasn't been used recently or very often.
*
* <p>When {@code size} is zero, elements can be successfully added to the map, but are evicted
* immediately. This has the same effect as invoking {@link #expireAfterWrite
* expireAfterWrite}{@code (0, unit)} or {@link #expireAfterAccess expireAfterAccess}{@code (0,
* unit)}. It can be useful in testing, or to disable caching temporarily without a code change.
*
* <p>Caching functionality in {@code MapMaker} is being moved to
* {@link com.google.common.cache.CacheBuilder}.
*
* @param size the maximum size of the map
* @throws IllegalArgumentException if {@code size} is negative
* @throws IllegalStateException if a maximum size was already set
* @deprecated Caching functionality in {@code MapMaker} is being moved to
* {@link com.google.common.cache.CacheBuilder}, with {@link #maximumSize} being
* replaced by {@link com.google.common.cache.CacheBuilder#maximumSize}. Note that {@code
* CacheBuilder} is simply an enhanced API for an implementation which was branched from
* {@code MapMaker}.
*/
@Deprecated
@Override
MapMaker maximumSize(int size) {
checkState(this.maximumSize == UNSET_INT, "maximum size was already set to %s",
this.maximumSize);
checkArgument(size >= 0, "maximum size must not be negative");
this.maximumSize = size;
this.useCustomMap = true;
if (maximumSize == 0) {
// SIZE trumps EXPIRED
this.nullRemovalCause = RemovalCause.SIZE;
}
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 assignment of entries to these partitions is not necessarily
* uniform, the actual concurrency observed may 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 permits only one thread to modify the map
* at a time, but since read operations can proceed concurrently, this still yields higher
* concurrency than full synchronization. Defaults to 4.
*
* <p><b>Note:</b> Prior to Guava release 9.0, the default was 16. It is possible the default will
* change again in the future. If you care about this value, you should always choose it
* explicitly.
*
* @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive
* @throws IllegalStateException if a concurrency level was already set
*/
@Override
public MapMaker concurrencyLevel(int concurrencyLevel) {
checkState(this.concurrencyLevel == UNSET_INT, "concurrency level was already set to %s",
this.concurrencyLevel);
checkArgument(concurrencyLevel > 0);
this.concurrencyLevel = concurrencyLevel;
return this;
}
int getConcurrencyLevel() {
return (concurrencyLevel == UNSET_INT) ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel;
}
/**
* Specifies that each key (not value) stored in the map should be wrapped in a {@link
* WeakReference} (by default, strong references are used).
*
* <p><b>Warning:</b> when this method is used, the resulting map will use identity ({@code ==})
* comparison to determine equality of keys, which is a technical violation of the {@link Map}
* specification, and may not be what you expect.
*
* @throws IllegalStateException if the key strength was already set
* @see WeakReference
*/
@GwtIncompatible("java.lang.ref.WeakReference")
@Override
public MapMaker weakKeys() {
return setKeyStrength(Strength.WEAK);
}
/**
* <b>This method is broken.</b> Maps with soft keys offer no functional advantage over maps with
* weak keys, and they waste memory by keeping unreachable elements in the map. If your goal is to
* create a memory-sensitive map, then consider using soft values instead.
*
* <p>Specifies that each key (not value) stored in the map should be wrapped in a
* {@link SoftReference} (by default, strong references are used). Softly-referenced objects will
* be garbage-collected in a <i>globally</i> least-recently-used manner, in response to memory
* demand.
*
* <p><b>Warning:</b> when this method is used, the resulting map will use identity ({@code ==})
* comparison to determine equality of keys, which is a technical violation of the {@link Map}
* specification, and may not be what you expect.
*
* @throws IllegalStateException if the key strength was already set
* @see SoftReference
* @deprecated use {@link #softValues} to create a memory-sensitive map, or {@link #weakKeys} to
* create a map that doesn't hold strong references to the keys.
* <b>This method is scheduled for deletion in January 2013.</b>
*/
@Deprecated
@GwtIncompatible("java.lang.ref.SoftReference")
@Override
public MapMaker softKeys() {
return setKeyStrength(Strength.SOFT);
}
MapMaker setKeyStrength(Strength strength) {
checkState(keyStrength == null, "Key strength was already set to %s", keyStrength);
keyStrength = checkNotNull(strength);
if (strength != Strength.STRONG) {
// STRONG could be used during deserialization.
useCustomMap = true;
}
return this;
}
Strength getKeyStrength() {
return firstNonNull(keyStrength, Strength.STRONG);
}
/**
* Specifies that each value (not key) stored in the map should be wrapped in a
* {@link WeakReference} (by default, strong references are used).
*
* <p>Weak values will be garbage collected once they are weakly reachable. This makes them a poor
* candidate for caching; consider {@link #softValues} instead.
*
* <p><b>Warning:</b> when this method is used, the resulting map will use identity ({@code ==})
* comparison to determine equality of values. This technically violates the specifications of
* the methods {@link Map#containsValue containsValue},
* {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)} and
* {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}, and may not be what you
* expect.
*
* @throws IllegalStateException if the value strength was already set
* @see WeakReference
*/
@GwtIncompatible("java.lang.ref.WeakReference")
@Override
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). Softly-referenced objects will
* be garbage-collected in a <i>globally</i> least-recently-used manner, in response to memory
* demand.
*
* <p><b>Warning:</b> in most circumstances it is better to set a per-cache {@linkplain
* #maximumSize maximum size} instead of using soft references. You should only use this method if
* you are well familiar with the practical consequences of soft references.
*
* <p><b>Warning:</b> when this method is used, the resulting map will use identity ({@code ==})
* comparison to determine equality of values. This technically violates the specifications of
* the methods {@link Map#containsValue containsValue},
* {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)} and
* {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}, and may not be what you
* expect.
*
* @throws IllegalStateException if the value strength was already set
* @see SoftReference
*/
@GwtIncompatible("java.lang.ref.SoftReference")
@Override
public MapMaker softValues() {
return setValueStrength(Strength.SOFT);
}
MapMaker setValueStrength(Strength strength) {
checkState(valueStrength == null, "Value strength was already set to %s", valueStrength);
valueStrength = checkNotNull(strength);
if (strength != Strength.STRONG) {
// STRONG could be used during deserialization.
useCustomMap = true;
}
return this;
}
Strength getValueStrength() {
return firstNonNull(valueStrength, Strength.STRONG);
}
/**
* Specifies that each entry should be automatically removed from the map once a fixed duration
* has elapsed after the entry's creation, or the most recent replacement of its value.
*
* <p>When {@code duration} is zero, elements can be successfully added to the map, but are
* evicted immediately. This has a very similar effect to invoking {@link #maximumSize
* maximumSize}{@code (0)}. It can be useful in testing, or to disable caching temporarily without
* a code change.
*
* <p>Expired entries may be counted by {@link Map#size}, but will never be visible to read or
* write operations. Expired entries are currently cleaned up during write operations, or during
* occasional read operations in the absense of writes; though this behavior may change in the
* future.
*
* @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 negative
* @throws IllegalStateException if the time to live or time to idle was already set
* @deprecated Caching functionality in {@code MapMaker} is being moved to
* {@link com.google.common.cache.CacheBuilder}, with {@link #expireAfterWrite} being
* replaced by {@link com.google.common.cache.CacheBuilder#expireAfterWrite}. Note that {@code
* CacheBuilder} is simply an enhanced API for an implementation which was branched from
* {@code MapMaker}.
*/
@Deprecated
@Override
MapMaker expireAfterWrite(long duration, TimeUnit unit) {
checkExpiration(duration, unit);
this.expireAfterWriteNanos = unit.toNanos(duration);
if (duration == 0 && this.nullRemovalCause == null) {
// SIZE trumps EXPIRED
this.nullRemovalCause = RemovalCause.EXPIRED;
}
useCustomMap = true;
return this;
}
private void checkExpiration(long duration, TimeUnit unit) {
checkState(expireAfterWriteNanos == UNSET_INT, "expireAfterWrite was already set to %s ns",
expireAfterWriteNanos);
checkState(expireAfterAccessNanos == UNSET_INT, "expireAfterAccess was already set to %s ns",
expireAfterAccessNanos);
checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);
}
long getExpireAfterWriteNanos() {
return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos;
}
/**
* Specifies that each entry should be automatically removed from the map once a fixed duration
* has elapsed after the entry's last read or write access.
*
* <p>When {@code duration} is zero, elements can be successfully added to the map, but are
* evicted immediately. This has a very similar effect to invoking {@link #maximumSize
* maximumSize}{@code (0)}. It can be useful in testing, or to disable caching temporarily without
* a code change.
*
* <p>Expired entries may be counted by {@link Map#size}, but will never be visible to read or
* write operations. Expired entries are currently cleaned up during write operations, or during
* occasional read operations in the absense of writes; though this behavior may change in the
* future.
*
* @param duration the length of time after an entry is last accessed that it should be
* automatically removed
* @param unit the unit that {@code duration} is expressed in
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the time to idle or time to live was already set
* @deprecated Caching functionality in {@code MapMaker} is being moved to
* {@link com.google.common.cache.CacheBuilder}, with {@link #expireAfterAccess} being
* replaced by {@link com.google.common.cache.CacheBuilder#expireAfterAccess}. Note that
* {@code CacheBuilder} is simply an enhanced API for an implementation which was branched
* from {@code MapMaker}.
*/
@Deprecated
@GwtIncompatible("To be supported")
@Override
MapMaker expireAfterAccess(long duration, TimeUnit unit) {
checkExpiration(duration, unit);
this.expireAfterAccessNanos = unit.toNanos(duration);
if (duration == 0 && this.nullRemovalCause == null) {
// SIZE trumps EXPIRED
this.nullRemovalCause = RemovalCause.EXPIRED;
}
useCustomMap = true;
return this;
}
long getExpireAfterAccessNanos() {
return (expireAfterAccessNanos == UNSET_INT)
? DEFAULT_EXPIRATION_NANOS : expireAfterAccessNanos;
}
Ticker getTicker() {
return firstNonNull(ticker, Ticker.systemTicker());
}
/**
* Specifies a listener instance, which all maps built using this {@code MapMaker} will notify
* each time an entry is removed from the map by any means.
*
* <p>Each map built by this map maker after this method is called invokes the supplied listener
* after removing an element for any reason (see removal causes in {@link RemovalCause}). It will
* invoke the listener during invocations of any of that map's public methods (even read-only
* methods).
*
* <p><b>Important note:</b> Instead of returning <i>this</i> as a {@code MapMaker} instance,
* this method returns {@code GenericMapMaker<K, V>}. From this point on, either the original
* reference or the returned reference may be used to complete configuration and build the map,
* but only the "generic" one is type-safe. That is, it will properly prevent you from building
* maps whose key or value types are incompatible with the types accepted by the listener already
* provided; the {@code MapMaker} type cannot do this. For best results, simply use the standard
* method-chaining idiom, as illustrated in the documentation at top, configuring a {@code
* MapMaker} and building your {@link Map} all in a single statement.
*
* <p><b>Warning:</b> if you ignore the above advice, and use this {@code MapMaker} to build a map
* or cache whose key or value type is incompatible with the listener, you will likely experience
* a {@link ClassCastException} at some <i>undefined</i> point in the future.
*
* @throws IllegalStateException if a removal listener was already set
* @deprecated Caching functionality in {@code MapMaker} is being moved to
* {@link com.google.common.cache.CacheBuilder}, with {@link #removalListener} being
* replaced by {@link com.google.common.cache.CacheBuilder#removalListener}. Note that {@code
* CacheBuilder} is simply an enhanced API for an implementation which was branched from
* {@code MapMaker}.
*/
@Deprecated
@GwtIncompatible("To be supported")
<K, V> GenericMapMaker<K, V> removalListener(RemovalListener<K, V> listener) {
checkState(this.removalListener == null);
// safely limiting the kinds of maps this can produce
@SuppressWarnings("unchecked")
GenericMapMaker<K, V> me = (GenericMapMaker<K, V>) this;
me.removalListener = checkNotNull(listener);
useCustomMap = true;
return me;
}
/**
* Builds a thread-safe map, without on-demand computation of values. This method does not alter
* the state of this {@code MapMaker} instance, so it can be invoked again to create multiple
* independent maps.
*
* <p>The bulk operations {@code putAll}, {@code equals}, and {@code clear} are not guaranteed to
* be performed atomically on the returned map. Additionally, {@code size} and {@code
* containsValue} are implemented as bulk read operations, and thus may fail to observe concurrent
* writes.
*
* @return a serializable concurrent map having the requested features
*/
@Override
public <K, V> ConcurrentMap<K, V> makeMap() {
if (!useCustomMap) {
return new ConcurrentHashMap<K, V>(getInitialCapacity(), 0.75f, getConcurrencyLevel());
}
return (nullRemovalCause == null)
? new MapMakerInternalMap<K, V>(this)
: new NullConcurrentMap<K, V>(this);
}
/**
* Returns a MapMakerInternalMap for the benefit of internal callers that use features of
* that class not exposed through ConcurrentMap.
*/
@Override
@GwtIncompatible("MapMakerInternalMap")
<K, V> MapMakerInternalMap<K, V> makeCustomMap() {
return new MapMakerInternalMap<K, V>(this);
}
/**
* Builds a map that supports atomic, on-demand computation of values. {@link Map#get} either
* returns an already-computed value for the given key, atomically computes it using the supplied
* function, or, if another thread is currently computing the value for this key, simply waits for
* that thread to finish and returns its computed value. Note that the function may be executed
* concurrently by multiple threads, but only for distinct keys.
*
* <p>New code should use {@link com.google.common.cache.CacheBuilder}, which supports
* {@linkplain com.google.common.cache.CacheStats statistics} collection, introduces the
* {@link com.google.common.cache.CacheLoader} interface for loading entries into the cache
* (allowing checked exceptions to be thrown in the process), and more cleanly separates
* computation from the cache's {@code Map} view.
*
* <p>If an entry's value has not finished computing yet, query methods besides {@code 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} on the returned map will never return {@code null}. It may throw:
*
* <ul>
* <li>{@link NullPointerException} if the key is null or the computing function returns a null
* result
* <li>{@link ComputationException} if an exception was thrown by the computing function. If that
* exception is already of type {@link ComputationException} it is propagated directly; otherwise
* it is wrapped.
* </ul>
*
* <p><b>Note:</b> Callers of {@code get} <i>must</i> ensure that the key argument is of type
* {@code K}. The {@code get} method accepts {@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 computing function as type {@code K}, and unsafely stored in the map.
*
* <p>If {@link Map#put} is called before a computation completes, other threads waiting on the
* computation will wake up and return the stored value.
*
* <p>This method does not alter the state of this {@code MapMaker} instance, so it can be invoked
* again to create multiple independent maps.
*
* <p>Insertion, removal, update, and access operations on the returned map safely execute
* concurrently by multiple threads. Iterators on the returned map are weakly consistent,
* returning elements reflecting the state of the map at some point at or since the creation of
* the iterator. They do not throw {@link ConcurrentModificationException}, and may proceed
* concurrently with other operations.
*
* <p>The bulk operations {@code putAll}, {@code equals}, and {@code clear} are not guaranteed to
* be performed atomically on the returned map. Additionally, {@code size} and {@code
* containsValue} are implemented as bulk read operations, and thus may fail to observe concurrent
* writes.
*
* @param computingFunction the function used to compute new values
* @return a serializable concurrent map having the requested features
* @deprecated Caching functionality in {@code MapMaker} is being moved to
* {@link com.google.common.cache.CacheBuilder}, with {@link #makeComputingMap} being replaced
* by {@link com.google.common.cache.CacheBuilder#build}. See the
* <a href="http://code.google.com/p/guava-libraries/wiki/MapMakerMigration">MapMaker
* Migration Guide</a> for more details.
* <b>This method is scheduled for deletion in February 2013.</b>
*/
@Deprecated
@Override
public <K, V> ConcurrentMap<K, V> makeComputingMap(
Function<? super K, ? extends V> computingFunction) {
return (nullRemovalCause == null)
? new ComputingMapAdapter<K, V>(this, computingFunction)
: new NullComputingConcurrentMap<K, V>(this, computingFunction);
}
/**
* Returns a string representation for this MapMaker instance. The exact form of the returned
* string is not specificed.
*/
@Override
public String toString() {
Objects.ToStringHelper s = Objects.toStringHelper(this);
if (initialCapacity != UNSET_INT) {
s.add("initialCapacity", initialCapacity);
}
if (concurrencyLevel != UNSET_INT) {
s.add("concurrencyLevel", concurrencyLevel);
}
if (maximumSize != UNSET_INT) {
s.add("maximumSize", maximumSize);
}
if (expireAfterWriteNanos != UNSET_INT) {
s.add("expireAfterWrite", expireAfterWriteNanos + "ns");
}
if (expireAfterAccessNanos != UNSET_INT) {
s.add("expireAfterAccess", expireAfterAccessNanos + "ns");
}
if (keyStrength != null) {
s.add("keyStrength", Ascii.toLowerCase(keyStrength.toString()));
}
if (valueStrength != null) {
s.add("valueStrength", Ascii.toLowerCase(valueStrength.toString()));
}
if (keyEquivalence != null) {
s.addValue("keyEquivalence");
}
if (removalListener != null) {
s.addValue("removalListener");
}
return s.toString();
}
/**
* An object that can receive a notification when an entry is removed from a map. The removal
* resulting in notification could have occured to an entry being manually removed or replaced, or
* due to eviction resulting from timed expiration, exceeding a maximum size, or garbage
* collection.
*
* <p>An instance may be called concurrently by multiple threads to process different entries.
* Implementations of this interface should avoid performing blocking calls or synchronizing on
* shared resources.
*
* @param <K> the most general type of keys this listener can listen for; for
* example {@code Object} if any key is acceptable
* @param <V> the most general type of values this listener can listen for; for
* example {@code Object} if any key is acceptable
*/
interface RemovalListener<K, V> {
/**
* Notifies the listener that a removal occurred at some point in the past.
*/
void onRemoval(RemovalNotification<K, V> notification);
}
/**
* A notification of the removal of a single entry. The key or value may be null if it was already
* garbage collected.
*
* <p>Like other {@code Map.Entry} instances associated with MapMaker, this class holds strong
* references to the key and value, regardless of the type of references the map may be using.
*/
static final class RemovalNotification<K, V> extends ImmutableEntry<K, V> {
private static final long serialVersionUID = 0;
private final RemovalCause cause;
RemovalNotification(@Nullable K key, @Nullable V value, RemovalCause cause) {
super(key, value);
this.cause = cause;
}
/**
* Returns the cause for which the entry was removed.
*/
public RemovalCause getCause() {
return cause;
}
/**
* Returns {@code true} if there was an automatic removal due to eviction (the cause is neither
* {@link RemovalCause#EXPLICIT} nor {@link RemovalCause#REPLACED}).
*/
public boolean wasEvicted() {
return cause.wasEvicted();
}
}
/**
* The reason why an entry was removed.
*/
enum RemovalCause {
/**
* The entry was manually removed by the user. This can result from the user invoking
* {@link Map#remove}, {@link ConcurrentMap#remove}, or {@link java.util.Iterator#remove}.
*/
EXPLICIT {
@Override
boolean wasEvicted() {
return false;
}
},
/**
* The entry itself was not actually removed, but its value was replaced by the user. This can
* result from the user invoking {@link Map#put}, {@link Map#putAll},
* {@link ConcurrentMap#replace(Object, Object)}, or
* {@link ConcurrentMap#replace(Object, Object, Object)}.
*/
REPLACED {
@Override
boolean wasEvicted() {
return false;
}
},
/**
* The entry was removed automatically because its key or value was garbage-collected. This
* can occur when using {@link #softKeys}, {@link #softValues}, {@link #weakKeys}, or {@link
* #weakValues}.
*/
COLLECTED {
@Override
boolean wasEvicted() {
return true;
}
},
/**
* The entry's expiration timestamp has passed. This can occur when using {@link
* #expireAfterWrite} or {@link #expireAfterAccess}.
*/
EXPIRED {
@Override
boolean wasEvicted() {
return true;
}
},
/**
* The entry was evicted due to size constraints. This can occur when using {@link
* #maximumSize}.
*/
SIZE {
@Override
boolean wasEvicted() {
return true;
}
};
/**
* Returns {@code true} if there was an automatic removal due to eviction (the cause is neither
* {@link #EXPLICIT} nor {@link #REPLACED}).
*/
abstract boolean wasEvicted();
}
/** A map that is always empty and evicts on insertion. */
static class NullConcurrentMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable {
private static final long serialVersionUID = 0;
private final RemovalListener<K, V> removalListener;
private final RemovalCause removalCause;
NullConcurrentMap(MapMaker mapMaker) {
removalListener = mapMaker.getRemovalListener();
removalCause = mapMaker.nullRemovalCause;
}
// implements ConcurrentMap
@Override
public boolean containsKey(@Nullable Object key) {
return false;
}
@Override
public boolean containsValue(@Nullable Object value) {
return false;
}
@Override
public V get(@Nullable Object key) {
return null;
}
void notifyRemoval(K key, V value) {
RemovalNotification<K, V> notification =
new RemovalNotification<K, V>(key, value, removalCause);
removalListener.onRemoval(notification);
}
@Override
public V put(K key, V value) {
checkNotNull(key);
checkNotNull(value);
notifyRemoval(key, value);
return null;
}
@Override
public V putIfAbsent(K key, V value) {
return put(key, value);
}
@Override
public V remove(@Nullable Object key) {
return null;
}
@Override
public boolean remove(@Nullable Object key, @Nullable Object value) {
return false;
}
@Override
public V replace(K key, V value) {
checkNotNull(key);
checkNotNull(value);
return null;
}
@Override
public boolean replace(K key, @Nullable V oldValue, V newValue) {
checkNotNull(key);
checkNotNull(newValue);
return false;
}
@Override
public Set<Entry<K, V>> entrySet() {
return Collections.emptySet();
}
}
/** Computes on retrieval and evicts the result. */
static final class NullComputingConcurrentMap<K, V> extends NullConcurrentMap<K, V> {
private static final long serialVersionUID = 0;
final Function<? super K, ? extends V> computingFunction;
NullComputingConcurrentMap(
MapMaker mapMaker, Function<? super K, ? extends V> computingFunction) {
super(mapMaker);
this.computingFunction = checkNotNull(computingFunction);
}
@SuppressWarnings("unchecked") // unsafe, which is why Cache is preferred
@Override
public V get(Object k) {
K key = (K) k;
V value = compute(key);
checkNotNull(value, computingFunction + " returned null for key " + key + ".");
notifyRemoval(key, value);
return value;
}
private V compute(K key) {
checkNotNull(key);
try {
return computingFunction.apply(key);
} catch (ComputationException e) {
throw e;
} catch (Throwable t) {
throw new ComputationException(t);
}
}
}
}
|