001 /* 002 * Copyright (C) 2009 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package com.google.common.collect; 018 019 import static com.google.common.base.Objects.firstNonNull; 020 import static com.google.common.base.Preconditions.checkArgument; 021 import static com.google.common.base.Preconditions.checkNotNull; 022 import static com.google.common.base.Preconditions.checkState; 023 024 import com.google.common.annotations.Beta; 025 import com.google.common.annotations.GwtCompatible; 026 import com.google.common.annotations.GwtIncompatible; 027 import com.google.common.base.Ascii; 028 import com.google.common.base.Equivalence; 029 import com.google.common.base.Function; 030 import com.google.common.base.Objects; 031 import com.google.common.base.Ticker; 032 import com.google.common.collect.CustomConcurrentHashMap.Strength; 033 034 import java.io.Serializable; 035 import java.lang.ref.SoftReference; 036 import java.lang.ref.WeakReference; 037 import java.util.AbstractMap; 038 import java.util.Collections; 039 import java.util.Map; 040 import java.util.Set; 041 import java.util.concurrent.ConcurrentHashMap; 042 import java.util.concurrent.ConcurrentMap; 043 import java.util.concurrent.Executor; 044 import java.util.concurrent.TimeUnit; 045 046 /** 047 * <p>A {@link ConcurrentMap} builder, providing any combination of these 048 * features: {@linkplain SoftReference soft} or {@linkplain WeakReference 049 * weak} keys, soft or weak values, size-based evicition, timed expiration, and 050 * on-demand computation of values. Usage example: <pre> {@code 051 * 052 * ConcurrentMap<Key, Graph> graphs = new MapMaker() 053 * .concurrencyLevel(4) 054 * .softKeys() 055 * .weakValues() 056 * .maximumSize(10000) 057 * .expireAfterWrite(10, TimeUnit.MINUTES) 058 * .makeComputingMap( 059 * new Function<Key, Graph>() { 060 * public Graph apply(Key key) { 061 * return createExpensiveGraph(key); 062 * } 063 * });}</pre> 064 * 065 * These features are all optional; {@code new MapMaker().makeMap()} 066 * returns a valid concurrent map that behaves exactly like a 067 * {@link ConcurrentHashMap}. 068 * 069 * The returned map is implemented as a hash table with similar performance 070 * characteristics to {@link ConcurrentHashMap}. It supports all optional 071 * operations of the {@code ConcurrentMap} interface. It does not permit 072 * null keys or values. 073 * 074 * <p><b>Note:</b> by default, the returned map uses equality comparisons 075 * (the {@link Object#equals(Object) equals} method) to determine equality 076 * for keys or values. However, if {@link #weakKeys()} or {@link 077 * #softKeys()} was specified, the map uses identity ({@code ==}) 078 * comparisons instead for keys. Likewise, if {@link #weakValues()} or 079 * {@link #softValues()} was specified, the map uses identity comparisons 080 * for values. 081 * 082 * <p>The returned map has <i>weakly consistent iteration</i>: an iterator 083 * over one of the map's view collections may reflect some, all or none of 084 * the changes made to the map after the iterator was created. 085 * 086 * <p>An entry whose key or value is reclaimed by the garbage collector 087 * immediately disappears from the map. (If the default settings of strong 088 * keys and strong values are used, this will never happen.) The client can 089 * never observe a partially-reclaimed entry. Any {@link java.util.Map.Entry} 090 * instance retrieved from the map's {@linkplain Map#entrySet() entry set} 091 * is a snapshot of that entry's state at the time of retrieval; such entries 092 * do, however, support {@link java.util.Map.Entry#setValue}. 093 * 094 * <p>The maps produced by {@code MapMaker} are serializable, and the 095 * deserialized maps retain all the configuration properties of the original 096 * map. If the map uses soft or weak references, the entries will be 097 * reconstructed as they were, but there is no guarantee that the entries won't 098 * be immediately reclaimed. 099 * 100 * <p>{@code new MapMaker().weakKeys().makeMap()} can almost always be 101 * used as a drop-in replacement for {@link java.util.WeakHashMap}, adding 102 * concurrency, asynchronous cleanup, identity-based equality for keys, and 103 * great flexibility. 104 * 105 * @author Bob Lee 106 * @author Kevin Bourrillion 107 * @since 2 (imported from Google Collections Library) 108 */ 109 @GwtCompatible(emulated = true) 110 public final class MapMaker extends GenericMapMaker<Object, Object> { 111 private static final int DEFAULT_INITIAL_CAPACITY = 16; 112 private static final int DEFAULT_CONCURRENCY_LEVEL = 4; 113 private static final int DEFAULT_EXPIRATION_NANOS = 0; 114 115 static final Executor DEFAULT_CLEANUP_EXECUTOR = 116 new Executor() { 117 @Override 118 public void execute(Runnable r) { 119 r.run(); 120 } 121 }; 122 123 static final Ticker DEFAULT_TICKER = 124 new Ticker() { 125 @Override 126 public long read() { 127 return System.nanoTime(); 128 } 129 }; 130 131 @SuppressWarnings("unchecked") 132 enum NullListener implements MapEvictionListener { 133 INSTANCE; 134 @Override public void onEviction(Object key, Object value) {} 135 } 136 137 static final int UNSET_INT = -1; 138 139 int initialCapacity = UNSET_INT; 140 int concurrencyLevel = UNSET_INT; 141 int maximumSize = UNSET_INT; 142 143 Strength keyStrength; 144 Strength valueStrength; 145 146 long expireAfterWriteNanos = UNSET_INT; 147 long expireAfterAccessNanos = UNSET_INT; 148 149 // TODO(kevinb): dispense with this after benchmarking 150 boolean useCustomMap; 151 boolean useNullMap; 152 153 Equivalence<Object> keyEquivalence; 154 Equivalence<Object> valueEquivalence; 155 156 Executor cleanupExecutor; 157 Ticker ticker; 158 159 /** 160 * Constructs a new {@code MapMaker} instance with default settings, 161 * including strong keys, strong values, and no automatic expiration. 162 */ 163 public MapMaker() {} 164 165 // TODO(kevinb): undo this indirection if keyEquiv gets released 166 MapMaker privateKeyEquivalence(Equivalence<Object> equivalence) { 167 checkState(keyEquivalence == null, 168 "key equivalence was already set to %s", keyEquivalence); 169 keyEquivalence = checkNotNull(equivalence); 170 this.useCustomMap = true; 171 return this; 172 } 173 174 Equivalence<Object> getKeyEquivalence() { 175 return firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence()); 176 } 177 178 // TODO(kevinb): undo this indirection if valueEquiv gets released 179 MapMaker privateValueEquivalence(Equivalence<Object> equivalence) { 180 checkState(valueEquivalence == null, 181 "value equivalence was already set to %s", valueEquivalence); 182 this.valueEquivalence = checkNotNull(equivalence); 183 this.useCustomMap = true; 184 return this; 185 } 186 187 Equivalence<Object> getValueEquivalence() { 188 return firstNonNull(valueEquivalence, 189 getValueStrength().defaultEquivalence()); 190 } 191 192 /** 193 * Sets a custom initial capacity (defaults to 16). Resizing this or 194 * any other kind of hash table is a relatively slow operation, so, 195 * when possible, it is a good idea to provide estimates of expected 196 * table sizes. 197 * 198 * @throws IllegalArgumentException if {@code initialCapacity} is 199 * negative 200 * @throws IllegalStateException if an initial capacity was already set 201 */ 202 @Override 203 public MapMaker initialCapacity(int initialCapacity) { 204 checkState(this.initialCapacity == UNSET_INT, 205 "initial capacity was already set to %s", this.initialCapacity); 206 checkArgument(initialCapacity >= 0); 207 this.initialCapacity = initialCapacity; 208 return this; 209 } 210 211 int getInitialCapacity() { 212 return (initialCapacity == UNSET_INT) 213 ? DEFAULT_INITIAL_CAPACITY : initialCapacity; 214 } 215 216 /** 217 * Specifies the maximum number of entries the map may contain. While the 218 * number of entries in the map is not guaranteed to grow to the maximum, 219 * the map will attempt to make the best use of memory without exceeding the 220 * maximum number of entries. As the map size grows close to the maximum, 221 * the map will evict entries that are less likely to be used again. For 222 * example, the map may evict an entry because it hasn't been used recently 223 * or very often. 224 * 225 * <p>When {@code size} is zero, elements can be successfully added to the 226 * map, but are evicted immediately. 227 * 228 * @param size the maximum size of the map 229 * 230 * @throws IllegalArgumentException if {@code size} is negative 231 * @throws IllegalStateException if a maximum size was already set 232 * @since 8 233 */ 234 @Beta 235 @Override 236 public MapMaker maximumSize(int size) { 237 checkState(this.maximumSize == UNSET_INT, 238 "maximum size was already set to %s", this.maximumSize); 239 checkArgument(size >= 0, "maximum size must not be negative"); 240 this.maximumSize = size; 241 this.useCustomMap = true; 242 this.useNullMap |= (maximumSize == 0); 243 return this; 244 } 245 246 /** 247 * Guides the allowed concurrency among update operations. Used as a 248 * hint for internal sizing. The table is internally partitioned to try 249 * to permit the indicated number of concurrent updates without 250 * contention. Because placement in hash tables is essentially random, 251 * the actual concurrency will vary. Ideally, you should choose a value 252 * to accommodate as many threads as will ever concurrently modify the 253 * table. Using a significantly higher value than you need can waste 254 * space and time, and a significantly lower value can lead to thread 255 * contention. But overestimates and underestimates within an order of 256 * magnitude do not usually have much noticeable impact. A value of one 257 * is appropriate when it is known that only one thread will modify and 258 * all others will only read. Defaults to 4. 259 * 260 * <p><b>Note:</b> Prior to Guava release 09, the default was 16. It is 261 * possible the default will change again in the future. If you care about 262 * this value, you should always choose it explicitly. 263 * 264 * @throws IllegalArgumentException if {@code concurrencyLevel} is 265 * nonpositive 266 * @throws IllegalStateException if a concurrency level was already set 267 */ 268 @GwtIncompatible("java.util.concurrent.ConcurrentHashMap concurrencyLevel") 269 @Override 270 public MapMaker concurrencyLevel(int concurrencyLevel) { 271 checkState(this.concurrencyLevel == UNSET_INT, 272 "concurrency level was already set to %s", this.concurrencyLevel); 273 checkArgument(concurrencyLevel > 0); 274 this.concurrencyLevel = concurrencyLevel; 275 return this; 276 } 277 278 int getConcurrencyLevel() { 279 return (concurrencyLevel == UNSET_INT) 280 ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel; 281 } 282 283 /** 284 * Specifies that each key (not value) stored in the map should be 285 * wrapped in a {@link WeakReference} (by default, strong references 286 * are used). 287 * 288 * <p><b>Note:</b> the map will use identity ({@code ==}) comparison 289 * to determine equality of weak keys, which may not behave as you expect. 290 * For example, storing a key in the map and then attempting a lookup 291 * using a different but {@link Object#equals(Object) equals}-equivalent 292 * key will always fail. 293 * 294 * @throws IllegalStateException if the key strength was already set 295 * @see WeakReference 296 */ 297 @GwtIncompatible("java.lang.ref.WeakReference") 298 @Override 299 public MapMaker weakKeys() { 300 return setKeyStrength(Strength.WEAK); 301 } 302 303 /** 304 * Specifies that each key (not value) stored in the map should be 305 * wrapped in a {@link SoftReference} (by default, strong references 306 * are used). 307 * 308 * <p><b>Note:</b> the map will use identity ({@code ==}) comparison 309 * to determine equality of soft keys, which may not behave as you expect. 310 * For example, storing a key in the map and then attempting a lookup 311 * using a different but {@link Object#equals(Object) equals}-equivalent 312 * key will always fail. 313 * 314 * @throws IllegalStateException if the key strength was already set 315 * @see SoftReference 316 */ 317 @GwtIncompatible("java.lang.ref.SoftReference") 318 @Override 319 public MapMaker softKeys() { 320 return setKeyStrength(Strength.SOFT); 321 } 322 323 MapMaker setKeyStrength(Strength strength) { 324 checkState(keyStrength == null, 325 "Key strength was already set to %s", keyStrength); 326 keyStrength = checkNotNull(strength); 327 if (strength != Strength.STRONG) { 328 // STRONG could be used during deserialization. 329 useCustomMap = true; 330 } 331 return this; 332 } 333 334 Strength getKeyStrength() { 335 return firstNonNull(keyStrength, Strength.STRONG); 336 } 337 338 /** 339 * Specifies that each value (not key) stored in the map should be 340 * wrapped in a {@link WeakReference} (by default, strong references 341 * are used). 342 * 343 * <p>Weak values will be garbage collected once they are weakly 344 * reachable. This makes them a poor candidate for caching; consider 345 * {@link #softValues()} instead. 346 * 347 * <p><b>Note:</b> the map will use identity ({@code ==}) comparison 348 * to determine equality of weak values. This will notably impact 349 * the behavior of {@link Map#containsValue(Object) containsValue}, 350 * {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)}, 351 * and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}. 352 * 353 * @throws IllegalStateException if the value strength was already set 354 * @see WeakReference 355 */ 356 @GwtIncompatible("java.lang.ref.WeakReference") 357 @Override 358 public MapMaker weakValues() { 359 return setValueStrength(Strength.WEAK); 360 } 361 362 /** 363 * Specifies that each value (not key) stored in the map should be 364 * wrapped in a {@link SoftReference} (by default, strong references 365 * are used). 366 * 367 * <p>Soft values will be garbage collected in response to memory 368 * demand, and in a least-recently-used manner. This makes them a 369 * good candidate for caching. 370 * 371 * <p><b>Note:</b> the map will use identity ({@code ==}) comparison 372 * to determine equality of soft values. This will notably impact 373 * the behavior of {@link Map#containsValue(Object) containsValue}, 374 * {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)}, 375 * and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}. 376 * 377 * @throws IllegalStateException if the value strength was already set 378 * @see SoftReference 379 */ 380 @GwtIncompatible("java.lang.ref.SoftReference") 381 @Override 382 public MapMaker softValues() { 383 return setValueStrength(Strength.SOFT); 384 } 385 386 MapMaker setValueStrength(Strength strength) { 387 checkState(valueStrength == null, 388 "Value strength was already set to %s", valueStrength); 389 valueStrength = checkNotNull(strength); 390 if (strength != Strength.STRONG) { 391 // STRONG could be used during deserialization. 392 useCustomMap = true; 393 } 394 return this; 395 } 396 397 Strength getValueStrength() { 398 return firstNonNull(valueStrength, Strength.STRONG); 399 } 400 401 /** 402 * Old name of {@link #expireAfterWrite}. 403 * 404 * @deprecated use {@link #expireAfterWrite}, which behaves exactly the same. 405 * <b>This method is scheduled for deletion in July 2012.</b> 406 */ 407 @Deprecated 408 @Override 409 public MapMaker expiration(long duration, TimeUnit unit) { 410 return expireAfterWrite(duration, unit); 411 } 412 413 /** 414 * Specifies that each entry should be automatically removed from the 415 * map once a fixed duration has passed since the entry's creation or 416 * replacement. Note that changing the value of an entry will reset its 417 * expiration time. 418 * 419 * <p>When {@code duration} is zero, elements can be successfully added to the 420 * map, but are evicted immediately. 421 * 422 * @param duration the length of time after an entry is created that it 423 * should be automatically removed 424 * @param unit the unit that {@code duration} is expressed in 425 * @throws IllegalArgumentException if {@code duration} is negative 426 * @throws IllegalStateException if the time to live or time to idle was 427 * already set 428 * @since 8 429 */ 430 @Beta 431 @Override 432 public MapMaker expireAfterWrite(long duration, TimeUnit unit) { 433 checkExpiration(duration, unit); 434 this.expireAfterWriteNanos = unit.toNanos(duration); 435 useNullMap |= (duration == 0); 436 useCustomMap = true; 437 return this; 438 } 439 440 private void checkExpiration(long duration, TimeUnit unit) { 441 checkState(expireAfterWriteNanos == UNSET_INT, 442 "expireAfterWrite was already set to %s ns", 443 expireAfterWriteNanos); 444 checkState(expireAfterAccessNanos == UNSET_INT, 445 "expireAfterAccess was already set to %s ns", 446 expireAfterAccessNanos); 447 checkArgument(duration >= 0, "duration cannot be negative: %s %s", 448 duration, unit); 449 } 450 451 long getExpireAfterWriteNanos() { 452 return (expireAfterWriteNanos == UNSET_INT) 453 ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; 454 } 455 456 /** 457 * Specifies that each entry should be automatically removed from the 458 * map once a fixed duration has passed since the entry's last read or 459 * write access. 460 * 461 * <p>When {@code duration} is zero, elements can be successfully added to the 462 * map, but are evicted immediately. 463 * 464 * @param duration the length of time after an entry is last accessed 465 * that it should be automatically removed 466 * @param unit the unit that {@code duration} is expressed in 467 * @throws IllegalArgumentException if {@code duration} is negative 468 * @throws IllegalStateException if the time to idle or time to live was 469 * already set 470 * @since 8 471 */ 472 @Beta 473 @GwtIncompatible("To be supported") 474 @Override 475 public MapMaker expireAfterAccess(long duration, TimeUnit unit) { 476 checkExpiration(duration, unit); 477 this.expireAfterAccessNanos = unit.toNanos(duration); 478 useNullMap |= (duration == 0); 479 useCustomMap = true; 480 return this; 481 } 482 483 long getExpireAfterAccessNanos() { 484 return (expireAfterAccessNanos == UNSET_INT) 485 ? DEFAULT_EXPIRATION_NANOS : expireAfterAccessNanos; 486 } 487 488 Executor getCleanupExecutor() { 489 return firstNonNull(cleanupExecutor, DEFAULT_CLEANUP_EXECUTOR); 490 } 491 492 Ticker getTicker() { 493 return firstNonNull(ticker, DEFAULT_TICKER); 494 } 495 496 /** 497 * Specifies a listener instance, which all maps built using this {@code 498 * MapMaker} will notify each time an entry is evicted. 499 * 500 * <p>A map built by this map maker will invoke the supplied listener after it 501 * evicts an entry, whether it does so due to timed expiration, exceeding the 502 * maximum size, or discovering that the key or value has been reclaimed by 503 * the garbage collector. It will invoke the listener synchronously, during 504 * invocations of any of that map's public methods (even read-only methods). 505 * The listener will <i>not</i> be invoked on manual removal. 506 * 507 * <p><b>Important note:</b> Instead of returning <em>this</em> as a {@code 508 * MapMaker} instance, this method returns {@code GenericMapMaker<K, V>}. 509 * From this point on, either the original reference or the returned 510 * reference may be used to complete configuration and build the map, but only 511 * the "generic" one is type-safe. That is, it will properly prevent you from 512 * building maps whose key or value types are incompatible with the types 513 * accepted by the listener already provided; the {@code MapMaker} type cannot 514 * do this. For best results, simply use the standard method-chaining idiom, 515 * as illustrated in the documentation at top, configuring a {@code MapMaker} 516 * and building your {@link Map} all in a single statement. 517 * 518 * <p><b>Warning:</b> if you ignore the above advice, and use this {@code 519 * MapMaker} to build maps whose key or value types are incompatible with the 520 * listener, you will likely experience a {@link ClassCastException} at an 521 * undefined point in the future. 522 * 523 * @throws IllegalStateException if an eviction listener was already set 524 * @since 7 525 */ 526 @Beta 527 @GwtIncompatible("To be supported") 528 public <K, V> GenericMapMaker<K, V> evictionListener( 529 MapEvictionListener<K, V> listener) { 530 checkState(this.evictionListener == null); 531 532 // safely limiting the kinds of maps this can produce 533 @SuppressWarnings("unchecked") 534 GenericMapMaker<K, V> me = (GenericMapMaker<K, V>) this; 535 me.evictionListener = checkNotNull(listener); 536 useCustomMap = true; 537 return me; 538 } 539 540 // TODO(kevinb): should this go in GenericMapMaker to avoid casts? 541 @SuppressWarnings("unchecked") 542 <K, V> MapEvictionListener<K, V> getEvictionListener() { 543 return evictionListener == null 544 ? (MapEvictionListener<K, V>) NullListener.INSTANCE 545 : (MapEvictionListener<K, V>) evictionListener; 546 } 547 548 /** 549 * Builds a map, without on-demand computation of values. This method 550 * does not alter the state of this {@code MapMaker} instance, so it can be 551 * invoked again to create multiple independent maps. 552 * 553 * @return a serializable concurrent map having the requested features 554 */ 555 @Override 556 public <K, V> ConcurrentMap<K, V> makeMap() { 557 if (!useCustomMap) { 558 return new ConcurrentHashMap<K, V>(getInitialCapacity(), 559 0.75f, getConcurrencyLevel()); 560 } 561 return useNullMap 562 ? new NullConcurrentMap<K, V>(this) 563 : new CustomConcurrentHashMap<K, V>(this); 564 } 565 566 /** 567 * Builds a caching function, which either returns an already-computed value 568 * for a given key or atomically computes it using the supplied function. 569 * If another thread is currently computing the value for this key, simply 570 * waits for that thread to finish and returns its computed value. Note that 571 * the function may be executed concurrently by multiple threads, but only for 572 * distinct keys. 573 * 574 * <p>The {@code Map} view of the {@code Cache}'s cache is only 575 * updated when function computation completes. In other words, an entry isn't 576 * visible until the value's computation completes. No methods on the {@code 577 * Map} will ever trigger computation. 578 * 579 * <p>{@link Cache#apply} in the returned function implementation may 580 * throw: 581 * 582 * <ul> 583 * <li>{@link NullPointerException} if the key is null or the 584 * computing function returns null 585 * <li>{@link ComputationException} if an exception was thrown by the 586 * computing function. If that exception is already of type {@link 587 * ComputationException} it is propagated directly; otherwise it is 588 * wrapped. 589 * </ul> 590 * 591 * <p>If {@link Map#put} is called before a computation completes, other 592 * threads waiting on the computation will wake up and return the stored 593 * value. When the computation completes, its result will be ignored. 594 * 595 * <p>This method does not alter the state of this {@code MapMaker} instance, 596 * so it can be invoked again to create multiple independent maps. 597 * 598 * @param computingFunction the function used to compute new values 599 * @return a serializable cache having the requested features 600 */ 601 // TODO(kevinb): figure out the Cache interface before making this public 602 <K, V> Cache<K, V> makeCache( 603 Function<? super K, ? extends V> computingFunction) { 604 return useNullMap 605 ? new NullComputingConcurrentMap<K, V>(this, computingFunction) 606 : new ComputingConcurrentHashMap<K, V>(this, computingFunction); 607 } 608 609 /** 610 * Builds a map that supports atomic, on-demand computation of values. {@link 611 * Map#get} either returns an already-computed value for the given key, 612 * atomically computes it using the supplied function, or, if another thread 613 * is currently computing the value for this key, simply waits for that thread 614 * to finish and returns its computed value. Note that the function may be 615 * executed concurrently by multiple threads, but only for distinct keys. 616 * 617 * <p>If an entry's value has not finished computing yet, query methods 618 * besides {@code get} return immediately as if an entry doesn't exist. In 619 * other words, an entry isn't externally visible until the value's 620 * computation completes. 621 * 622 * <p>{@link Map#get} on the returned map will never return {@code null}. It 623 * may throw: 624 * 625 * <ul> 626 * <li>{@link NullPointerException} if the key is null or the computing 627 * function returns null 628 * <li>{@link ComputationException} if an exception was thrown by the 629 * computing function. If that exception is already of type {@link 630 * ComputationException} it is propagated directly; otherwise it is 631 * wrapped. 632 * </ul> 633 * 634 * <p><b>Note:</b> Callers of {@code get} <i>must</i> ensure that the key 635 * argument is of type {@code K}. The {@code get} method accepts {@code 636 * Object}, so the key type is not checked at compile time. Passing an object 637 * of a type other than {@code K} can result in that object being unsafely 638 * passed to the computing function as type {@code K}, and unsafely stored in 639 * the map. 640 * 641 * <p>If {@link Map#put} is called before a computation completes, other 642 * threads waiting on the computation will wake up and return the stored 643 * value. 644 * 645 * <p>This method does not alter the state of this {@code MapMaker} instance, 646 * so it can be invoked again to create multiple independent maps. 647 * 648 * @param computingFunction the function used to compute new values 649 * @return a serializable concurrent map having the requested features 650 */ 651 @Override 652 public <K, V> ConcurrentMap<K, V> makeComputingMap( 653 Function<? super K, ? extends V> computingFunction) { 654 Cache<K, V> cache = makeCache(computingFunction); 655 return new ComputingMapAdapter<K, V>(cache); 656 } 657 658 /** 659 * Returns a string representation for this MapMaker instance. 660 * The form of this representation is not guaranteed. 661 */ 662 @Override 663 public String toString() { 664 Objects.ToStringHelper s = Objects.toStringHelper(this); 665 if (initialCapacity != UNSET_INT) { 666 s.add("initialCapacity", initialCapacity); 667 } 668 if (concurrencyLevel != UNSET_INT) { 669 s.add("concurrencyLevel", concurrencyLevel); 670 } 671 if (maximumSize != UNSET_INT) { 672 s.add("maximumSize", maximumSize); 673 } 674 if (expireAfterWriteNanos != UNSET_INT) { 675 s.add("expireAfterWrite", expireAfterWriteNanos + "ns"); 676 } 677 if (expireAfterAccessNanos != UNSET_INT) { 678 s.add("expireAfterAccess", expireAfterAccessNanos + "ns"); 679 } 680 if (keyStrength != null) { 681 s.add("keyStrength", Ascii.toLowerCase(keyStrength.toString())); 682 } 683 if (valueStrength != null) { 684 s.add("valueStrength", Ascii.toLowerCase(valueStrength.toString())); 685 } 686 if (keyEquivalence != null) { 687 s.addValue("keyEquivalence"); 688 } 689 if (valueEquivalence != null) { 690 s.addValue("valueEquivalence"); 691 } 692 if (evictionListener != null) { 693 s.addValue("evictionListener"); 694 } 695 if (cleanupExecutor != null) { 696 s.addValue("cleanupExecutor"); 697 } 698 return s.toString(); 699 } 700 701 /** 702 * A function which caches the result of each application (computation). This 703 * interface does not specify the caching semantics, but does expose a {@code 704 * ConcurrentMap} view of cached entries. 705 */ 706 interface Cache<K, V> extends Function<K, V> { 707 708 /** 709 * Returns a map view of the cached entries. 710 */ 711 ConcurrentMap<K, V> asMap(); 712 } 713 714 /** A map that is always empty and evicts on insertion. */ 715 static class NullConcurrentMap<K, V> extends AbstractMap<K, V> 716 implements ConcurrentMap<K, V>, Serializable { 717 private static final long serialVersionUID = 0; 718 719 final MapEvictionListener<K, V> evictionListener; 720 721 NullConcurrentMap(MapMaker mapMaker) { 722 evictionListener = mapMaker.getEvictionListener(); 723 } 724 725 @Override 726 public boolean containsKey(Object key) { 727 checkNotNull(key); 728 return false; 729 } 730 731 @Override 732 public boolean containsValue(Object value) { 733 checkNotNull(value); 734 return false; 735 } 736 737 @Override 738 public V get(Object key) { 739 checkNotNull(key); 740 return null; 741 } 742 743 @Override 744 public V put(K key, V value) { 745 checkNotNull(key); 746 checkNotNull(value); 747 evictionListener.onEviction(key, value); 748 return null; 749 } 750 751 @Override 752 public V putIfAbsent(K key, V value) { 753 return put(key, value); 754 } 755 756 @Override 757 public V remove(Object key) { 758 checkNotNull(key); 759 return null; 760 } 761 762 @Override 763 public boolean remove(Object key, Object value) { 764 checkNotNull(key); 765 checkNotNull(value); 766 return false; 767 } 768 769 @Override 770 public V replace(K key, V value) { 771 checkNotNull(key); 772 checkNotNull(value); 773 return null; 774 } 775 776 @Override 777 public boolean replace(K key, V oldValue, V newValue) { 778 checkNotNull(key); 779 checkNotNull(oldValue); 780 checkNotNull(newValue); 781 return false; 782 } 783 784 @Override 785 public Set<Entry<K, V>> entrySet() { 786 return Collections.emptySet(); 787 } 788 } 789 790 /** Computes on retrieval and evicts the result. */ 791 static final class NullComputingConcurrentMap<K, V> 792 extends NullConcurrentMap<K, V> implements Cache<K, V> { 793 private static final long serialVersionUID = 0; 794 795 final Function<? super K, ? extends V> computingFunction; 796 797 NullComputingConcurrentMap(MapMaker mapMaker, 798 Function<? super K, ? extends V> computingFunction) { 799 super(mapMaker); 800 this.computingFunction = checkNotNull(computingFunction); 801 } 802 803 @Override 804 public V apply(K key) { 805 V value = compute(key); 806 checkNotNull(value, 807 computingFunction + " returned null for key " + key + "."); 808 evictionListener.onEviction(key, value); 809 return value; 810 } 811 812 private V compute(K key) { 813 checkNotNull(key); 814 try { 815 return computingFunction.apply(key); 816 } catch (ComputationException e) { 817 throw e; 818 } catch (Throwable t) { 819 throw new ComputationException(t); 820 } 821 } 822 823 @Override 824 public ConcurrentMap<K, V> asMap() { 825 return this; 826 } 827 } 828 829 /** 830 * Overrides get() to compute on demand. 831 */ 832 static class ComputingMapAdapter<K, V> extends ForwardingConcurrentMap<K, V> 833 implements Serializable { 834 private static final long serialVersionUID = 0; 835 836 final Cache<K, V> cache; 837 838 ComputingMapAdapter(Cache<K, V> cache) { 839 this.cache = cache; 840 } 841 842 @Override protected ConcurrentMap<K, V> delegate() { 843 return cache.asMap(); 844 } 845 846 @SuppressWarnings("unchecked") // unsafe, which is why this is deprecated 847 @Override public V get(Object key) { 848 return cache.apply((K) key); 849 } 850 } 851 }