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