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 @Override 062 public ImmutableMap<K, V> getAllPresent(Iterable<?> keys) { 063 Map<K, V> result = Maps.newLinkedHashMap(); 064 for (Object key : keys) { 065 if (!result.containsKey(key)) { 066 @SuppressWarnings("unchecked") 067 K castKey = (K) key; 068 V value = getIfPresent(key); 069 if (value != null) { 070 result.put(castKey, value); 071 } 072 } 073 } 074 return ImmutableMap.copyOf(result); 075 } 076 077 /** @since 11.0 */ 078 @Override 079 public void put(K key, V value) { 080 throw new UnsupportedOperationException(); 081 } 082 083 /** @since 12.0 */ 084 @Override 085 public void putAll(Map<? extends K, ? extends V> m) { 086 for (Entry<? extends K, ? extends V> entry : m.entrySet()) { 087 put(entry.getKey(), entry.getValue()); 088 } 089 } 090 091 @Override 092 public void cleanUp() {} 093 094 @Override 095 public long size() { 096 throw new UnsupportedOperationException(); 097 } 098 099 @Override 100 public void invalidate(Object key) { 101 throw new UnsupportedOperationException(); 102 } 103 104 /** @since 11.0 */ 105 @Override 106 public void invalidateAll(Iterable<?> keys) { 107 for (Object key : keys) { 108 invalidate(key); 109 } 110 } 111 112 @Override 113 public void invalidateAll() { 114 throw new UnsupportedOperationException(); 115 } 116 117 @Override 118 public CacheStats stats() { 119 throw new UnsupportedOperationException(); 120 } 121 122 @Override 123 public ConcurrentMap<K, V> asMap() { 124 throw new UnsupportedOperationException(); 125 } 126 127 /** 128 * Accumulates statistics during the operation of a {@link Cache} for presentation by {@link 129 * Cache#stats}. This is solely intended for consumption by {@code Cache} implementors. 130 * 131 * @since 10.0 132 */ 133 public interface StatsCounter { 134 /** 135 * Records cache hits. This should be called when a cache request returns a cached value. 136 * 137 * @param count the number of hits to record 138 * @since 11.0 139 */ 140 void recordHits(int count); 141 142 /** 143 * Records cache misses. This should be called when a cache request returns a value that was not 144 * found in the cache. This method should be called by the loading thread, as well as by threads 145 * blocking on the load. Multiple concurrent calls to {@link Cache} lookup methods with the same 146 * key on an absent value should result in a single call to either {@code recordLoadSuccess} or 147 * {@code recordLoadException} and multiple calls to this method, despite all being served by 148 * the results of a single load operation. 149 * 150 * @param count the number of misses to record 151 * @since 11.0 152 */ 153 void recordMisses(int count); 154 155 /** 156 * Records the successful load of a new entry. This should be called when a cache request causes 157 * an entry to be loaded, and the loading completes successfully. In contrast to {@link 158 * #recordMisses}, this method should only be called by the loading thread. 159 * 160 * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new 161 * value 162 */ 163 @SuppressWarnings("GoodTime") // should accept a java.time.Duration 164 void recordLoadSuccess(long loadTime); 165 166 /** 167 * Records the failed load of a new entry. This should be called when a cache request causes an 168 * entry to be loaded, but an exception is thrown while loading the entry. In contrast to {@link 169 * #recordMisses}, this method should only be called by the loading thread. 170 * 171 * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new 172 * value prior to an exception being thrown 173 */ 174 @SuppressWarnings("GoodTime") // should accept a java.time.Duration 175 void recordLoadException(long loadTime); 176 177 /** 178 * Records the eviction of an entry from the cache. This should only been called when an entry 179 * is evicted due to the cache's eviction strategy, and not as a result of manual {@linkplain 180 * Cache#invalidate invalidations}. 181 */ 182 void recordEviction(); 183 184 /** 185 * Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as 186 * it may be interleaved with update operations. 187 */ 188 CacheStats snapshot(); 189 } 190 191 /** 192 * A thread-safe {@link StatsCounter} implementation for use by {@link Cache} implementors. 193 * 194 * @since 10.0 195 */ 196 public static final class SimpleStatsCounter implements StatsCounter { 197 private final LongAddable hitCount = LongAddables.create(); 198 private final LongAddable missCount = LongAddables.create(); 199 private final LongAddable loadSuccessCount = LongAddables.create(); 200 private final LongAddable loadExceptionCount = LongAddables.create(); 201 private final LongAddable totalLoadTime = LongAddables.create(); 202 private final LongAddable evictionCount = LongAddables.create(); 203 204 /** Constructs an instance with all counts initialized to zero. */ 205 public SimpleStatsCounter() {} 206 207 /** @since 11.0 */ 208 @Override 209 public void recordHits(int count) { 210 hitCount.add(count); 211 } 212 213 /** @since 11.0 */ 214 @Override 215 public void recordMisses(int count) { 216 missCount.add(count); 217 } 218 219 @SuppressWarnings("GoodTime") // b/122668874 220 @Override 221 public void recordLoadSuccess(long loadTime) { 222 loadSuccessCount.increment(); 223 totalLoadTime.add(loadTime); 224 } 225 226 @SuppressWarnings("GoodTime") // b/122668874 227 @Override 228 public void recordLoadException(long loadTime) { 229 loadExceptionCount.increment(); 230 totalLoadTime.add(loadTime); 231 } 232 233 @Override 234 public void recordEviction() { 235 evictionCount.increment(); 236 } 237 238 @Override 239 public CacheStats snapshot() { 240 return new CacheStats( 241 negativeToMaxValue(hitCount.sum()), 242 negativeToMaxValue(missCount.sum()), 243 negativeToMaxValue(loadSuccessCount.sum()), 244 negativeToMaxValue(loadExceptionCount.sum()), 245 negativeToMaxValue(totalLoadTime.sum()), 246 negativeToMaxValue(evictionCount.sum())); 247 } 248 249 /** Returns {@code value}, if non-negative. Otherwise, returns {@link Long#MAX_VALUE}. */ 250 private static long negativeToMaxValue(long value) { 251 return (value >= 0) ? value : Long.MAX_VALUE; 252 } 253 254 /** Increments all counters by the values in {@code other}. */ 255 public void incrementBy(StatsCounter other) { 256 CacheStats otherStats = other.snapshot(); 257 hitCount.add(otherStats.hitCount()); 258 missCount.add(otherStats.missCount()); 259 loadSuccessCount.add(otherStats.loadSuccessCount()); 260 loadExceptionCount.add(otherStats.loadExceptionCount()); 261 totalLoadTime.add(otherStats.totalLoadTime()); 262 evictionCount.add(otherStats.evictionCount()); 263 } 264 } 265}