001/* 002 * Copyright (C) 2011 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.cache; 016 017import com.google.common.annotations.GwtCompatible; 018import com.google.common.collect.ImmutableMap; 019import com.google.common.collect.Maps; 020import java.util.Map; 021import java.util.Map.Entry; 022import java.util.concurrent.Callable; 023import java.util.concurrent.ConcurrentMap; 024import java.util.concurrent.ExecutionException; 025 026/** 027 * This class provides a skeletal implementation of the {@code Cache} interface to minimize the 028 * effort required to implement this interface. 029 * 030 * <p>To implement a cache, the programmer needs only to extend this class and provide an 031 * implementation for the {@link #put} and {@link #getIfPresent} methods. {@link #getAllPresent} is 032 * implemented in terms of {@link #getIfPresent}; {@link #putAll} is implemented in terms of {@link 033 * #put}, {@link #invalidateAll(Iterable)} is implemented in terms of {@link #invalidate}. The 034 * method {@link #cleanUp} is a no-op. All other methods throw an {@link 035 * UnsupportedOperationException}. 036 * 037 * @author Charles Fry 038 * @since 10.0 039 */ 040@GwtCompatible 041public abstract class AbstractCache<K, V> implements Cache<K, V> { 042 043 /** Constructor for use by subclasses. */ 044 protected AbstractCache() {} 045 046 /** @since 11.0 */ 047 @Override 048 public V get(K key, Callable<? extends V> valueLoader) throws ExecutionException { 049 throw new UnsupportedOperationException(); 050 } 051 052 /** 053 * {@inheritDoc} 054 * 055 * <p>This implementation of {@code getAllPresent} lacks any insight into the internal cache data 056 * structure, and is thus forced to return the query keys instead of the cached keys. This is only 057 * possible with an unsafe cast which requires {@code keys} to actually be of type {@code K}. 058 * 059 * @since 11.0 060 */ 061 /* 062 * <? extends Object> is mostly the same as <?> to plain Java. But to nullness checkers, they 063 * differ: <? extends Object> means "non-null types," while <?> means "all types." 064 */ 065 @Override 066 public ImmutableMap<K, V> getAllPresent(Iterable<? extends Object> keys) { 067 Map<K, V> result = Maps.newLinkedHashMap(); 068 for (Object key : keys) { 069 if (!result.containsKey(key)) { 070 @SuppressWarnings("unchecked") 071 K castKey = (K) key; 072 V value = getIfPresent(key); 073 if (value != null) { 074 result.put(castKey, value); 075 } 076 } 077 } 078 return ImmutableMap.copyOf(result); 079 } 080 081 /** @since 11.0 */ 082 @Override 083 public void put(K key, V value) { 084 throw new UnsupportedOperationException(); 085 } 086 087 /** @since 12.0 */ 088 @Override 089 public void putAll(Map<? extends K, ? extends V> m) { 090 for (Entry<? extends K, ? extends V> entry : m.entrySet()) { 091 put(entry.getKey(), entry.getValue()); 092 } 093 } 094 095 @Override 096 public void cleanUp() {} 097 098 @Override 099 public long size() { 100 throw new UnsupportedOperationException(); 101 } 102 103 @Override 104 public void invalidate(Object key) { 105 throw new UnsupportedOperationException(); 106 } 107 108 /** @since 11.0 */ 109 @Override 110 // For discussion of <? extends Object>, see getAllPresent. 111 public void invalidateAll(Iterable<? extends Object> keys) { 112 for (Object key : keys) { 113 invalidate(key); 114 } 115 } 116 117 @Override 118 public void invalidateAll() { 119 throw new UnsupportedOperationException(); 120 } 121 122 @Override 123 public CacheStats stats() { 124 throw new UnsupportedOperationException(); 125 } 126 127 @Override 128 public ConcurrentMap<K, V> asMap() { 129 throw new UnsupportedOperationException(); 130 } 131 132 /** 133 * Accumulates statistics during the operation of a {@link Cache} for presentation by {@link 134 * Cache#stats}. This is solely intended for consumption by {@code Cache} implementors. 135 * 136 * @since 10.0 137 */ 138 public interface StatsCounter { 139 /** 140 * Records cache hits. This should be called when a cache request returns a cached value. 141 * 142 * @param count the number of hits to record 143 * @since 11.0 144 */ 145 void recordHits(int count); 146 147 /** 148 * Records cache misses. This should be called when a cache request returns a value that was not 149 * found in the cache. This method should be called by the loading thread, as well as by threads 150 * blocking on the load. Multiple concurrent calls to {@link Cache} lookup methods with the same 151 * key on an absent value should result in a single call to either {@code recordLoadSuccess} or 152 * {@code recordLoadException} and multiple calls to this method, despite all being served by 153 * the results of a single load operation. 154 * 155 * @param count the number of misses to record 156 * @since 11.0 157 */ 158 void recordMisses(int count); 159 160 /** 161 * Records the successful load of a new entry. This should be called when a cache request causes 162 * an entry to be loaded, and the loading completes successfully. In contrast to {@link 163 * #recordMisses}, this method should only be called by the loading thread. 164 * 165 * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new 166 * value 167 */ 168 @SuppressWarnings("GoodTime") // should accept a java.time.Duration 169 void recordLoadSuccess(long loadTime); 170 171 /** 172 * Records the failed load of a new entry. This should be called when a cache request causes an 173 * entry to be loaded, but an exception is thrown while loading the entry. In contrast to {@link 174 * #recordMisses}, this method should only be called by the loading thread. 175 * 176 * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new 177 * value prior to an exception being thrown 178 */ 179 @SuppressWarnings("GoodTime") // should accept a java.time.Duration 180 void recordLoadException(long loadTime); 181 182 /** 183 * Records the eviction of an entry from the cache. This should only been called when an entry 184 * is evicted due to the cache's eviction strategy, and not as a result of manual {@linkplain 185 * Cache#invalidate invalidations}. 186 */ 187 void recordEviction(); 188 189 /** 190 * Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as 191 * it may be interleaved with update operations. 192 */ 193 CacheStats snapshot(); 194 } 195 196 /** 197 * A thread-safe {@link StatsCounter} implementation for use by {@link Cache} implementors. 198 * 199 * @since 10.0 200 */ 201 public static final class SimpleStatsCounter implements StatsCounter { 202 private final LongAddable hitCount = LongAddables.create(); 203 private final LongAddable missCount = LongAddables.create(); 204 private final LongAddable loadSuccessCount = LongAddables.create(); 205 private final LongAddable loadExceptionCount = LongAddables.create(); 206 private final LongAddable totalLoadTime = LongAddables.create(); 207 private final LongAddable evictionCount = LongAddables.create(); 208 209 /** Constructs an instance with all counts initialized to zero. */ 210 public SimpleStatsCounter() {} 211 212 /** @since 11.0 */ 213 @Override 214 public void recordHits(int count) { 215 hitCount.add(count); 216 } 217 218 /** @since 11.0 */ 219 @Override 220 public void recordMisses(int count) { 221 missCount.add(count); 222 } 223 224 @SuppressWarnings("GoodTime") // b/122668874 225 @Override 226 public void recordLoadSuccess(long loadTime) { 227 loadSuccessCount.increment(); 228 totalLoadTime.add(loadTime); 229 } 230 231 @SuppressWarnings("GoodTime") // b/122668874 232 @Override 233 public void recordLoadException(long loadTime) { 234 loadExceptionCount.increment(); 235 totalLoadTime.add(loadTime); 236 } 237 238 @Override 239 public void recordEviction() { 240 evictionCount.increment(); 241 } 242 243 @Override 244 public CacheStats snapshot() { 245 return new CacheStats( 246 negativeToMaxValue(hitCount.sum()), 247 negativeToMaxValue(missCount.sum()), 248 negativeToMaxValue(loadSuccessCount.sum()), 249 negativeToMaxValue(loadExceptionCount.sum()), 250 negativeToMaxValue(totalLoadTime.sum()), 251 negativeToMaxValue(evictionCount.sum())); 252 } 253 254 /** Returns {@code value}, if non-negative. Otherwise, returns {@link Long#MAX_VALUE}. */ 255 private static long negativeToMaxValue(long value) { 256 return (value >= 0) ? value : Long.MAX_VALUE; 257 } 258 259 /** Increments all counters by the values in {@code other}. */ 260 public void incrementBy(StatsCounter other) { 261 CacheStats otherStats = other.snapshot(); 262 hitCount.add(otherStats.hitCount()); 263 missCount.add(otherStats.missCount()); 264 loadSuccessCount.add(otherStats.loadSuccessCount()); 265 loadExceptionCount.add(otherStats.loadExceptionCount()); 266 totalLoadTime.add(otherStats.totalLoadTime()); 267 evictionCount.add(otherStats.evictionCount()); 268 } 269 } 270}