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