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