• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
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 
17 package com.google.common.cache;
18 
19 import static com.google.common.base.MoreObjects.firstNonNull;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.base.Preconditions.checkState;
22 
23 import com.google.common.base.Equivalence;
24 import com.google.common.base.Ticker;
25 import com.google.common.cache.AbstractCache.StatsCounter;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.util.concurrent.ExecutionError;
28 import com.google.common.util.concurrent.UncheckedExecutionException;
29 import com.google.errorprone.annotations.CanIgnoreReturnValue;
30 import java.util.AbstractCollection;
31 import java.util.AbstractSet;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.LinkedHashMap;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 import java.util.NoSuchElementException;
39 import java.util.Set;
40 import java.util.concurrent.Callable;
41 import java.util.concurrent.ConcurrentMap;
42 import java.util.concurrent.ExecutionException;
43 import org.checkerframework.checker.nullness.qual.Nullable;
44 
45 /**
46  * LocalCache emulation for GWT.
47  *
48  * @param <K> the base key type
49  * @param <V> the base value type
50  * @author Charles Fry
51  * @author Jon Donovan
52  */
53 public class LocalCache<K, V> implements ConcurrentMap<K, V> {
54   private static final int UNSET_INT = CacheBuilder.UNSET_INT;
55 
56   private final LinkedHashMap<K, Timestamped<V>> cachingHashMap;
57   private final CacheLoader<? super K, V> loader;
58   private final RemovalListener removalListener;
59   private final StatsCounter statsCounter;
60   private final Ticker ticker;
61   private final long expireAfterWrite;
62   private final long expireAfterAccess;
63 
LocalCache(CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader)64   LocalCache(CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) {
65     this.loader = loader;
66     this.removalListener = builder.removalListener;
67     this.expireAfterAccess = builder.expireAfterAccessNanos;
68     this.expireAfterWrite = builder.expireAfterWriteNanos;
69     this.statsCounter = builder.getStatsCounterSupplier().get();
70 
71     /* Implements size-capped LinkedHashMap */
72     final long maximumSize = builder.maximumSize;
73     this.cachingHashMap =
74         new CapacityEnforcingLinkedHashMap<K, V>(
75             builder.getInitialCapacity(),
76             0.75f,
77             (builder.maximumSize != UNSET_INT),
78             builder.maximumSize,
79             statsCounter,
80             removalListener);
81 
82     this.ticker = firstNonNull(builder.ticker, Ticker.systemTicker());
83   }
84 
85   @Override
size()86   public int size() {
87     return cachingHashMap.size();
88   }
89 
90   @Override
isEmpty()91   public boolean isEmpty() {
92     return cachingHashMap.isEmpty();
93   }
94 
95   @Override
get(Object key)96   public V get(Object key) {
97     checkNotNull(key);
98     Timestamped<V> value = cachingHashMap.get(key);
99 
100     if (value == null) {
101       statsCounter.recordMisses(1);
102       return null;
103     } else if (!isExpired(value)) {
104       statsCounter.recordHits(1);
105       value.updateTimestamp();
106       return value.getValue();
107     } else {
108       statsCounter.recordEviction();
109       statsCounter.recordMisses(1);
110       alertListenerIfPresent(key, value.getValue(), RemovalCause.EXPIRED);
111       cachingHashMap.remove(key);
112       return null;
113     }
114   }
115 
116   @CanIgnoreReturnValue
117   @Override
put(K key, V value)118   public V put(K key, V value) {
119     checkNotNull(key);
120     checkNotNull(value);
121     Timestamped<V> oldValue = cachingHashMap.put(key, new Timestamped<V>(value, ticker));
122     if (oldValue == null) {
123       return null;
124     }
125     alertListenerIfPresent(key, oldValue.getValue(), RemovalCause.REPLACED);
126     return oldValue.getValue();
127   }
128 
129   @CanIgnoreReturnValue
130   @Override
remove(Object key)131   public V remove(Object key) {
132     Timestamped<V> stamped = cachingHashMap.remove(key);
133     if (stamped != null) {
134       V value = stamped.getValue();
135 
136       if (!isExpired(stamped)) {
137         alertListenerIfPresent(key, value, RemovalCause.EXPLICIT);
138         return value;
139       }
140 
141       alertListenerIfPresent(key, value, RemovalCause.EXPIRED);
142     }
143     return null;
144   }
145 
146   @Override
putAll(Map<? extends K, ? extends V> m)147   public void putAll(Map<? extends K, ? extends V> m) {
148     for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
149       put(entry.getKey(), entry.getValue());
150     }
151   }
152 
153   @Override
clear()154   public void clear() {
155     if (removalListener != null) {
156       for (Entry<K, Timestamped<V>> entry : cachingHashMap.entrySet()) {
157         alertListenerIfPresent(entry.getKey(), entry.getValue().getValue(), RemovalCause.EXPLICIT);
158       }
159     }
160     cachingHashMap.clear();
161   }
162 
163   @Override
putIfAbsent(K key, V value)164   public V putIfAbsent(K key, V value) {
165     V currentValue = get(key);
166     if (currentValue != null) {
167       return currentValue;
168     }
169     return put(key, value);
170   }
171 
172   @CanIgnoreReturnValue
173   @Override
remove(Object key, Object value)174   public boolean remove(Object key, Object value) {
175     if (value.equals(get(key))) {
176       alertListenerIfPresent(key, value, RemovalCause.EXPLICIT);
177       remove(key);
178       return true;
179     }
180     return false;
181   }
182 
183   @Override
replace(K key, V oldValue, V newValue)184   public boolean replace(K key, V oldValue, V newValue) {
185     if (oldValue.equals(get(key))) {
186       alertListenerIfPresent(key, oldValue, RemovalCause.REPLACED);
187       put(key, newValue);
188       return true;
189     }
190     return false;
191   }
192 
193   @Override
replace(K key, V value)194   public V replace(K key, V value) {
195     V currentValue = get(key);
196     if (currentValue != null) {
197       alertListenerIfPresent(key, currentValue, RemovalCause.REPLACED);
198       return put(key, value);
199     }
200     return null;
201   }
202 
203   @Override
containsKey(Object key)204   public boolean containsKey(Object key) {
205     return cachingHashMap.containsKey(key) && !isExpired(cachingHashMap.get(key));
206   }
207 
208   @Override
containsValue(Object value)209   public boolean containsValue(Object value) {
210     for (Timestamped<V> val : cachingHashMap.values()) {
211       if (val.getValue().equals(value)) {
212         if (!isExpired(val)) {
213           return true;
214         }
215       }
216     }
217     return false;
218   }
219 
isExpired(Timestamped<V> stamped)220   private boolean isExpired(Timestamped<V> stamped) {
221     if ((expireAfterAccess == UNSET_INT) && (expireAfterWrite == UNSET_INT)) {
222       return false;
223     }
224 
225     boolean expireWrite = (stamped.getWriteTimestamp() + expireAfterWrite <= currentTimeNanos());
226     boolean expireAccess = (stamped.getAccessTimestamp() + expireAfterAccess <= currentTimeNanos());
227 
228     if (expireAfterAccess == UNSET_INT) {
229       return expireWrite;
230     }
231     if (expireAfterWrite == UNSET_INT) {
232       return expireAccess;
233     }
234 
235     return expireWrite || expireAccess;
236   }
237 
238   @SuppressWarnings("GoodTime")
currentTimeNanos()239   private long currentTimeNanos() {
240     return ticker.read();
241   }
242 
alertListenerIfPresent(Object key, Object value, RemovalCause cause)243   private void alertListenerIfPresent(Object key, Object value, RemovalCause cause) {
244     if (removalListener != null) {
245       removalListener.onRemoval(RemovalNotification.create(key, value, cause));
246     }
247   }
248 
249   @SuppressWarnings("GoodTime") // timestamps as numeric primitives
load(Object key)250   private V load(Object key) throws ExecutionException {
251     long startTime = ticker.read();
252     V calculatedValue;
253     try {
254       /*
255        * This cast isn't safe, but we can rely on the fact that K is almost always passed to
256        * Map.get(), and tools like IDEs and Findbugs can catch situations where this isn't the
257        * case.
258        *
259        * The alternative is to add an overloaded method, but the chances of a user calling get()
260        * instead of the new API and the risks inherent in adding a new API outweigh this little
261        * hole.
262        */
263       K castKey = (K) key;
264       calculatedValue = loader.load(castKey);
265       put(castKey, calculatedValue);
266     } catch (RuntimeException e) {
267       statsCounter.recordLoadException(ticker.read() - startTime);
268       throw new UncheckedExecutionException(e);
269     } catch (Exception e) {
270       statsCounter.recordLoadException(ticker.read() - startTime);
271       throw new ExecutionException(e);
272     } catch (Error e) {
273       statsCounter.recordLoadException(ticker.read() - startTime);
274       throw new ExecutionError(e);
275     }
276 
277     if (calculatedValue == null) {
278       String message = loader + " returned null for key " + key + ".";
279       throw new CacheLoader.InvalidCacheLoadException(message);
280     }
281     statsCounter.recordLoadSuccess(ticker.read() - startTime);
282     return calculatedValue;
283   }
284 
getIfPresent(Object key)285   private V getIfPresent(Object key) {
286     checkNotNull(key);
287     Timestamped<V> value = cachingHashMap.get(key);
288 
289     if (value == null) {
290       return null;
291     } else if (!isExpired(value)) {
292       value.updateTimestamp();
293       return value.getValue();
294     } else {
295       alertListenerIfPresent(key, value.getValue(), RemovalCause.EXPIRED);
296       cachingHashMap.remove(key);
297       return null;
298     }
299   }
300 
getOrLoad(K key)301   private V getOrLoad(K key) throws ExecutionException {
302     V value = get(key);
303     if (value != null) {
304       return value;
305     }
306     return load(key);
307   }
308 
309   @SuppressWarnings("GoodTime") // timestamps as numeric primitives
310   private static class Timestamped<V> {
311     private final V value;
312     private final Ticker ticker;
313     private long writeTimestamp;
314     private long accessTimestamp;
315 
Timestamped(V value, Ticker ticker)316     public Timestamped(V value, Ticker ticker) {
317       this.value = checkNotNull(value);
318       this.ticker = checkNotNull(ticker);
319       this.writeTimestamp = ticker.read();
320       this.accessTimestamp = this.writeTimestamp;
321     }
322 
getValue()323     public V getValue() {
324       return value;
325     }
326 
updateTimestamp()327     public void updateTimestamp() {
328       accessTimestamp = ticker.read();
329     }
330 
getAccessTimestamp()331     public long getAccessTimestamp() {
332       return accessTimestamp;
333     }
334 
getWriteTimestamp()335     public long getWriteTimestamp() {
336       return writeTimestamp;
337     }
338 
equals(Object o)339     public boolean equals(Object o) {
340       return value.equals(o);
341     }
342 
hashCode()343     public int hashCode() {
344       return value.hashCode();
345     }
346   }
347 
348   /**
349    * LocalManualCache is a wrapper around LocalCache for a cache without loading.
350    *
351    * @param <K> the base key type
352    * @param <V> the base value type
353    */
354   public static class LocalManualCache<K, V> extends AbstractCache<K, V> {
355     final LocalCache<K, V> localCache;
356 
LocalManualCache(CacheBuilder<? super K, ? super V> builder)357     LocalManualCache(CacheBuilder<? super K, ? super V> builder) {
358       this(builder, null);
359     }
360 
LocalManualCache( CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader)361     protected LocalManualCache(
362         CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) {
363       this.localCache = new LocalCache<K, V>(builder, loader);
364     }
365 
366     // Cache methods
367 
368     @Override
get(K key, Callable<? extends V> valueLoader)369     public V get(K key, Callable<? extends V> valueLoader) throws ExecutionException {
370       V value = localCache.get(key);
371       if (value != null) {
372         return value;
373       }
374 
375       try {
376         V newValue = valueLoader.call();
377         localCache.put(key, newValue);
378         return newValue;
379       } catch (Exception e) {
380         throw new ExecutionException(e);
381       }
382     }
383 
384     @Override
getIfPresent(Object key)385     public @Nullable V getIfPresent(Object key) {
386       return localCache.getIfPresent(key);
387     }
388 
389     @Override
put(K key, V value)390     public void put(K key, V value) {
391       localCache.put(key, value);
392     }
393 
394     @Override
invalidate(Object key)395     public void invalidate(Object key) {
396       checkNotNull(key);
397       localCache.remove(key);
398     }
399 
400     @Override
invalidateAll()401     public void invalidateAll() {
402       localCache.clear();
403     }
404 
405     @Override
size()406     public long size() {
407       return localCache.size();
408     }
409 
410     @Override
asMap()411     public ConcurrentMap<K, V> asMap() {
412       return localCache;
413     }
414   }
415 
416   /**
417    * LocalLoadingCache is a wrapper around LocalCache for a cache with loading.
418    *
419    * @param <K> the base key type
420    * @param <V> the base value type
421    */
422   public static class LocalLoadingCache<K, V> extends LocalManualCache<K, V>
423       implements LoadingCache<K, V> {
424 
LocalLoadingCache( CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader)425     LocalLoadingCache(
426         CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) {
427       super(builder, checkNotNull(loader));
428     }
429 
430     // Cache methods
431 
432     @Override
get(K key)433     public V get(K key) throws ExecutionException {
434       return localCache.getOrLoad(key);
435     }
436 
437     @Override
getUnchecked(K key)438     public V getUnchecked(K key) {
439       try {
440         return get(key);
441       } catch (ExecutionException e) {
442         throw new UncheckedExecutionException(e.getCause());
443       }
444     }
445 
446     @Override
apply(K key)447     public final V apply(K key) {
448       return getUnchecked(key);
449     }
450 
451     @Override
getAll(Iterable<? extends K> keys)452     public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
453       Map<K, V> map = new HashMap<K, V>();
454       for (K key : keys) {
455         map.put(key, localCache.getOrLoad(key));
456       }
457       return ImmutableMap.copyOf(map);
458     }
459 
460     @Override
refresh(K key)461     public void refresh(K key) {
462       throw new UnsupportedOperationException();
463     }
464   }
465 
466   /**
467    * LinkedHashMap that enforces it's maximum size and logs events in a StatsCounter object and an
468    * optional RemovalListener.
469    *
470    * @param <K> the base key type
471    * @param <V> the base value type
472    */
473   private class CapacityEnforcingLinkedHashMap<K, V> extends LinkedHashMap<K, Timestamped<V>> {
474 
475     private final StatsCounter statsCounter;
476     private final RemovalListener removalListener;
477     private final long maximumSize;
478 
CapacityEnforcingLinkedHashMap( int initialCapacity, float loadFactor, boolean accessOrder, long maximumSize, StatsCounter statsCounter, @Nullable RemovalListener removalListener)479     public CapacityEnforcingLinkedHashMap(
480         int initialCapacity,
481         float loadFactor,
482         boolean accessOrder,
483         long maximumSize,
484         StatsCounter statsCounter,
485         @Nullable RemovalListener removalListener) {
486       super(initialCapacity, loadFactor, accessOrder);
487       this.maximumSize = maximumSize;
488       this.statsCounter = statsCounter;
489       this.removalListener = removalListener;
490     }
491 
492     @Override
removeEldestEntry(Entry<K, Timestamped<V>> ignored)493     protected boolean removeEldestEntry(Entry<K, Timestamped<V>> ignored) {
494       boolean removal = (maximumSize == UNSET_INT) ? false : (size() > maximumSize);
495       if ((removalListener != null) && removal) {
496         removalListener.onRemoval(
497             RemovalNotification.create(
498                 ignored.getKey(), ignored.getValue().getValue(), RemovalCause.SIZE));
499       }
500       statsCounter.recordEviction();
501       return removal;
502     }
503   }
504 
505   /**
506    * Any updates to LocalCache.Strength used in CacheBuilder need to be matched in this class for
507    * compilation purposes.
508    */
509   enum Strength {
510     /*
511      * TODO(kevinb): If we strongly reference the value and aren't loading, we needn't wrap the
512      * value. This could save ~8 bytes per entry.
513      */
514 
515     STRONG {
516       @Override
defaultEquivalence()517       Equivalence<Object> defaultEquivalence() {
518         return Equivalence.equals();
519       }
520     },
521 
522     SOFT {
523       @Override
defaultEquivalence()524       Equivalence<Object> defaultEquivalence() {
525         return Equivalence.identity();
526       }
527     },
528 
529     WEAK {
530       @Override
defaultEquivalence()531       Equivalence<Object> defaultEquivalence() {
532         return Equivalence.identity();
533       }
534     };
535 
defaultEquivalence()536     abstract Equivalence<Object> defaultEquivalence();
537   }
538 
539   /**
540    * Implementation for the EntryIterator, which is used to build Key and Value iterators.
541    *
542    * <p>Expiration is only checked on hasNext(), so as to ensure that a next() call never returns
543    * null when hasNext() has already been called.
544    */
545   class EntryIterator implements Iterator<Entry<K, V>> {
546     Iterator<Entry<K, Timestamped<V>>> iterator;
547     Entry<K, Timestamped<V>> lastEntry;
548     Entry<K, Timestamped<V>> nextEntry;
549 
EntryIterator()550     EntryIterator() {
551       this.iterator = LocalCache.this.cachingHashMap.entrySet().iterator();
552     }
553 
554     @Override
next()555     public Entry<K, V> next() {
556       if (nextEntry == null) {
557         boolean unused = hasNext();
558 
559         if (nextEntry == null) {
560           throw new NoSuchElementException();
561         }
562       }
563 
564       lastEntry = nextEntry;
565       nextEntry = null;
566       return new WriteThroughEntry(lastEntry.getKey(), lastEntry.getValue().getValue());
567     }
568 
569     @Override
hasNext()570     public boolean hasNext() {
571       if (nextEntry == null) {
572         while (iterator.hasNext()) {
573           Entry<K, Timestamped<V>> next = iterator.next();
574           if (!isExpired(next.getValue())) {
575             nextEntry = next;
576             return true;
577           }
578         }
579         return false;
580       }
581       return true;
582     }
583 
584     @Override
remove()585     public void remove() {
586       checkState(lastEntry != null);
587       LocalCache.this.remove(lastEntry.getKey(), lastEntry.getValue());
588       lastEntry = null;
589     }
590   }
591 
592   /** KeyIterator build on top of EntryIterator. */
593   final class KeyIterator implements Iterator<K> {
594     private EntryIterator iterator;
595 
KeyIterator()596     KeyIterator() {
597       iterator = new EntryIterator();
598     }
599 
600     @Override
hasNext()601     public boolean hasNext() {
602       return iterator.hasNext();
603     }
604 
605     @Override
next()606     public K next() {
607       return iterator.next().getKey();
608     }
609 
610     @Override
remove()611     public void remove() {
612       iterator.remove();
613     }
614   }
615 
616   /** ValueIterator build on top of EntryIterator. */
617   final class ValueIterator implements Iterator<V> {
618     private EntryIterator iterator;
619 
ValueIterator()620     ValueIterator() {
621       iterator = new EntryIterator();
622     }
623 
624     @Override
hasNext()625     public boolean hasNext() {
626       return iterator.hasNext();
627     }
628 
629     @Override
next()630     public V next() {
631       return iterator.next().getValue();
632     }
633 
634     @Override
remove()635     public void remove() {
636       iterator.remove();
637     }
638   }
639 
640   Set<K> keySet;
641 
642   @Override
keySet()643   public Set<K> keySet() {
644     // does not impact recency ordering
645     Set<K> ks = keySet;
646     return (ks != null) ? ks : (keySet = new KeySet(this));
647   }
648 
649   Collection<V> values;
650 
651   @Override
values()652   public Collection<V> values() {
653     // does not impact recency ordering
654     Collection<V> vs = values;
655     return (vs != null) ? vs : (values = new Values(this));
656   }
657 
658   Set<Entry<K, V>> entrySet;
659 
660   @Override
entrySet()661   public Set<Entry<K, V>> entrySet() {
662     // does not impact recency ordering
663     Set<Entry<K, V>> es = entrySet;
664     return (es != null) ? es : (entrySet = new EntrySet(this));
665   }
666 
667   /**
668    * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the underlying
669    * map.
670    */
671   private final class WriteThroughEntry implements Entry<K, V> {
672     final K key;
673     V value;
674 
WriteThroughEntry(K key, V value)675     WriteThroughEntry(K key, V value) {
676       this.key = checkNotNull(key);
677       this.value = checkNotNull(value);
678     }
679 
680     @Override
getKey()681     public K getKey() {
682       return key;
683     }
684 
685     @Override
getValue()686     public V getValue() {
687       return value;
688     }
689 
690     @Override
equals(@ullable Object object)691     public boolean equals(@Nullable Object object) {
692       // Cannot use key and value equivalence
693       if (object instanceof Entry) {
694         Entry<?, ?> that = (Entry<?, ?>) object;
695         return key.equals(that.getKey()) && value.equals(that.getValue());
696       }
697       return false;
698     }
699 
700     @Override
hashCode()701     public int hashCode() {
702       // Cannot use key and value equivalence
703       return key.hashCode() ^ value.hashCode();
704     }
705 
706     @Override
setValue(V newValue)707     public V setValue(V newValue) {
708       throw new UnsupportedOperationException();
709     }
710 
711     @Override
toString()712     public String toString() {
713       return getKey() + "=" + getValue();
714     }
715   }
716 
717   // TODO(fry): Separate logic for consistency between emul and nonemul implementation.
718   // TODO(fry): Look into Maps.KeySet and Maps.Values, which can ideally be reused here but are
719   // currently only package visible.
720   abstract class AbstractCacheSet<T> extends AbstractSet<T> {
721     final ConcurrentMap<?, ?> map;
722 
AbstractCacheSet(ConcurrentMap<?, ?> map)723     AbstractCacheSet(ConcurrentMap<?, ?> map) {
724       this.map = map;
725     }
726 
727     @Override
size()728     public int size() {
729       return map.size();
730     }
731 
732     @Override
isEmpty()733     public boolean isEmpty() {
734       return map.isEmpty();
735     }
736 
737     @Override
clear()738     public void clear() {
739       map.clear();
740     }
741   }
742 
743   private final class KeySet extends AbstractCacheSet<K> {
744 
KeySet(ConcurrentMap<?, ?> map)745     KeySet(ConcurrentMap<?, ?> map) {
746       super(map);
747     }
748 
749     @Override
iterator()750     public Iterator<K> iterator() {
751       return new KeyIterator();
752     }
753 
754     @Override
contains(Object o)755     public boolean contains(Object o) {
756       return map.containsKey(o);
757     }
758 
759     @Override
remove(Object o)760     public boolean remove(Object o) {
761       return map.remove(o) != null;
762     }
763   }
764 
765   private final class Values extends AbstractCollection<V> {
766     final ConcurrentMap<?, ?> map;
767 
Values(ConcurrentMap<?, ?> map)768     Values(ConcurrentMap<?, ?> map) {
769       this.map = map;
770     }
771 
772     @Override
iterator()773     public Iterator<V> iterator() {
774       return new ValueIterator();
775     }
776 
777     @Override
contains(Object o)778     public boolean contains(Object o) {
779       return map.containsValue(o);
780     }
781 
782     @Override
size()783     public int size() {
784       return map.size();
785     }
786 
787     @Override
isEmpty()788     public boolean isEmpty() {
789       return map.isEmpty();
790     }
791 
792     @Override
clear()793     public void clear() {
794       map.clear();
795     }
796   }
797 
798   private final class EntrySet extends AbstractCacheSet<Entry<K, V>> {
799 
EntrySet(ConcurrentMap<?, ?> map)800     EntrySet(ConcurrentMap<?, ?> map) {
801       super(map);
802     }
803 
804     @Override
iterator()805     public Iterator<Entry<K, V>> iterator() {
806       return new EntryIterator();
807     }
808 
809     @Override
contains(Object o)810     public boolean contains(Object o) {
811       if (!(o instanceof Entry)) {
812         return false;
813       }
814       Entry<?, ?> e = (Entry<?, ?>) o;
815       Object key = e.getKey();
816       if (key == null) {
817         return false;
818       }
819       V v = LocalCache.this.get(key);
820 
821       return (v != null) && e.getValue().equals(v);
822     }
823 
824     @Override
remove(Object o)825     public boolean remove(Object o) {
826       if (!(o instanceof Entry)) {
827         return false;
828       }
829       Entry<?, ?> e = (Entry<?, ?>) o;
830       Object key = e.getKey();
831       return (key != null) && LocalCache.this.remove(key, e.getValue());
832     }
833   }
834 }
835