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.util.concurrent.UncheckedExecutionException;
021    
022    import java.util.concurrent.ConcurrentMap;
023    import java.util.concurrent.ExecutionException;
024    import java.util.concurrent.atomic.AtomicLong;
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 {@code get} method. This implementation throws an
032     * {@link UnsupportedOperationException} on calls to {@link #size}, {@link #invalidate},
033     * {@link #invalidateAll}, {@link #stats}, and {@link #asMap}. The methods
034     * {@link #getUnchecked} and {@link #apply} are implemented in terms of {@link #get}. The method
035     * {@link #cleanUp} is a no-op.
036     *
037     * @author Charles Fry
038     * @since 10.0
039     */
040    @Beta
041    public abstract class AbstractCache<K, V> implements Cache<K, V> {
042    
043      /** Constructor for use by subclasses. */
044      protected AbstractCache() {}
045    
046      @Override
047      public V getUnchecked(K key) {
048        try {
049          return get(key);
050        } catch (ExecutionException e) {
051          throw new UncheckedExecutionException(e.getCause());
052        }
053      }
054    
055      @Override
056      public final V apply(K key) {
057        return getUnchecked(key);
058      }
059    
060      @Override
061      public void cleanUp() {}
062    
063      @Override
064      public long size() {
065        throw new UnsupportedOperationException();
066      }
067    
068      @Override
069      public void invalidate(Object key) {
070        throw new UnsupportedOperationException();
071      }
072    
073      @Override
074      public void invalidateAll() {
075        throw new UnsupportedOperationException();
076      }
077    
078      @Override
079      public CacheStats stats() {
080        throw new UnsupportedOperationException();
081      }
082    
083      @Override
084      public ConcurrentMap<K, V> asMap() {
085        throw new UnsupportedOperationException();
086      }
087    
088      /**
089       * Accumulates statistics during the operation of a {@link Cache} for presentation by {@link
090       * Cache#stats}. This is solely intended for consumption by {@code Cache} implementors.
091       *
092       * @since 10.0
093       */
094      @Beta
095      public interface StatsCounter {
096        /**
097         * Records a single hit. This should be called when a cache request returns a cached value.
098         */
099        public void recordHit();
100    
101        /**
102         * Records the successful load of a new entry. This should be called when a cache request
103         * causes an entry to be loaded, and the loading completes succesfully. In contrast to
104         * {@link #recordConcurrentMiss}, this method should only be called by the loading thread.
105         *
106         * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new
107         *     value
108         */
109        public void recordLoadSuccess(long loadTime);
110    
111        /**
112         * Records the failed load of a new entry. This should be called when a cache request causes
113         * an entry to be loaded, but an exception is thrown while loading the entry. In contrast to
114         * {@link #recordConcurrentMiss}, this method should only be called by the loading thread.
115         *
116         * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new
117         *     value prior to an exception being thrown
118         */
119        public void recordLoadException(long loadTime);
120    
121        /**
122         * Records a single concurrent miss. This should be called when a cache request returns a
123         * value which was loaded by a different thread. In contrast to {@link #recordLoadSuccess}
124         * and {@link #recordLoadException}, this method should never be called by the loading
125         * thread. Multiple concurrent calls to {@link Cache} lookup methods with the same key on an
126         * absent value should result in a single call to either {@code recordLoadSuccess} or
127         * {@code recordLoadException} and multiple calls to this method, despite all being served by
128         * the results of a single load operation.
129         */
130        public void recordConcurrentMiss();
131    
132        /**
133         * Records the eviction of an entry from the cache. This should only been called when an entry
134         * is evicted due to the cache's eviction strategy, and not as a result of manual {@linkplain
135         * Cache#invalidate invalidations}.
136         */
137        public void recordEviction();
138    
139        /**
140         * Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as
141         * it may be interleaved with update operations.
142         */
143        public CacheStats snapshot();
144      }
145    
146      /**
147       * A thread-safe {@link StatsCounter} implementation for use by {@link Cache} implementors.
148       *
149       * @since 10.0
150       */
151      @Beta
152      public static class SimpleStatsCounter implements StatsCounter {
153        private final AtomicLong hitCount = new AtomicLong();
154        private final AtomicLong missCount = new AtomicLong();
155        private final AtomicLong loadSuccessCount = new AtomicLong();
156        private final AtomicLong loadExceptionCount = new AtomicLong();
157        private final AtomicLong totalLoadTime = new AtomicLong();
158        private final AtomicLong evictionCount = new AtomicLong();
159    
160        @Override
161        public void recordHit() {
162          hitCount.incrementAndGet();
163        }
164    
165        @Override
166        public void recordLoadSuccess(long loadTime) {
167          missCount.incrementAndGet();
168          loadSuccessCount.incrementAndGet();
169          totalLoadTime.addAndGet(loadTime);
170        }
171    
172        @Override
173        public void recordLoadException(long loadTime) {
174          missCount.incrementAndGet();
175          loadExceptionCount.incrementAndGet();
176          totalLoadTime.addAndGet(loadTime);
177        }
178    
179        @Override
180        public void recordConcurrentMiss() {
181          missCount.incrementAndGet();
182        }
183    
184        @Override
185        public void recordEviction() {
186          evictionCount.incrementAndGet();
187        }
188    
189        @Override
190        public CacheStats snapshot() {
191          return new CacheStats(
192              hitCount.get(),
193              missCount.get(),
194              loadSuccessCount.get(),
195              loadExceptionCount.get(),
196              totalLoadTime.get(),
197              evictionCount.get());
198        }
199    
200        /**
201         * Increments all counters by the values in {@code other}.
202         */
203        public void incrementBy(StatsCounter other) {
204          CacheStats otherStats = other.snapshot();
205          hitCount.addAndGet(otherStats.hitCount());
206          missCount.addAndGet(otherStats.missCount());
207          loadSuccessCount.addAndGet(otherStats.loadSuccessCount());
208          loadExceptionCount.addAndGet(otherStats.loadExceptionCount());
209          totalLoadTime.addAndGet(otherStats.totalLoadTime());
210          evictionCount.addAndGet(otherStats.evictionCount());
211        }
212      }
213    }