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