|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifndef NET_BASE_EXPIRING_CACHE_H_ | 
|  | #define NET_BASE_EXPIRING_CACHE_H_ | 
|  |  | 
|  | #include <map> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/gtest_prod_util.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/time/time.h" | 
|  | #include "starboard/types.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | template <typename KeyType, | 
|  | typename ValueType, | 
|  | typename ExpirationType> | 
|  | class NoopEvictionHandler { | 
|  | public: | 
|  | void Handle(const KeyType& /*key*/, | 
|  | const ValueType& /*value*/, | 
|  | const ExpirationType& /*expiration*/, | 
|  | const ExpirationType& /*now*/, | 
|  | bool /*onGet*/) const {} | 
|  | }; | 
|  |  | 
|  | // Cache implementation where all entries have an explicit expiration policy. As | 
|  | // new items are added, expired items will be removed first. | 
|  | // The template types have the following requirements: | 
|  | //  KeyType must be LessThanComparable, Assignable, and CopyConstructible. | 
|  | //  ValueType must be CopyConstructible and Assignable. | 
|  | //  ExpirationType must be CopyConstructible and Assignable. | 
|  | //  ExpirationCompare is a function class that takes two arguments of the | 
|  | //    type ExpirationType and returns a bool. If |comp| is an instance of | 
|  | //    ExpirationCompare, then the expression |comp(current, expiration)| shall | 
|  | //    return true iff |current| is still valid within |expiration|. | 
|  | // | 
|  | // A simple use of this class may use base::TimeTicks, which provides a | 
|  | // monotonically increasing clock, for the expiration type. Because it's always | 
|  | // increasing, std::less<> can be used, which will simply ensure that |now| is | 
|  | // sorted before |expiration|: | 
|  | // | 
|  | //   ExpiringCache<std::string, std::string, base::TimeTicks, | 
|  | //                 std::less<base::TimeTicks> > cache(0); | 
|  | //   // Add a value that expires in 5 minutes | 
|  | //   cache.Put("key1", "value1", base::TimeTicks::Now(), | 
|  | //             base::TimeTicks::Now() + base::TimeDelta::FromMinutes(5)); | 
|  | //   // Add another value that expires in 10 minutes. | 
|  | //   cache.Put("key2", "value2", base::TimeTicks::Now(), | 
|  | //             base::TimeTicks::Now() + base::TimeDelta::FromMinutes(10)); | 
|  | // | 
|  | // Alternatively, there may be some more complex expiration criteria, at which | 
|  | // point a custom functor may be used: | 
|  | // | 
|  | //   struct ComplexExpirationFunctor { | 
|  | //     bool operator()(const ComplexExpiration& now, | 
|  | //                     const ComplexExpiration& expiration) const; | 
|  | //   }; | 
|  | //   ExpiringCache<std::string, std::string, ComplexExpiration, | 
|  | //                 ComplexExpirationFunctor> cache(15); | 
|  | //   // Add a value that expires once the 'sprocket' has 'cog'-ified. | 
|  | //   cache.Put("key1", "value1", ComplexExpiration("sprocket"), | 
|  | //             ComplexExpiration("cog")); | 
|  | template <typename KeyType, | 
|  | typename ValueType, | 
|  | typename ExpirationType, | 
|  | typename ExpirationCompare, | 
|  | typename EvictionHandler = NoopEvictionHandler<KeyType, | 
|  | ValueType, | 
|  | ExpirationType> > | 
|  | class ExpiringCache { | 
|  | private: | 
|  | // Intentionally violate the C++ Style Guide so that EntryMap is known to be | 
|  | // a dependent type. Without this, Clang's two-phase lookup complains when | 
|  | // using EntryMap::const_iterator, while GCC and MSVC happily resolve the | 
|  | // typename. | 
|  |  | 
|  | // Tuple to represent the value and when it expires. | 
|  | typedef std::pair<ValueType, ExpirationType> Entry; | 
|  | typedef std::map<KeyType, Entry> EntryMap; | 
|  |  | 
|  | public: | 
|  | typedef KeyType key_type; | 
|  | typedef ValueType value_type; | 
|  | typedef ExpirationType expiration_type; | 
|  |  | 
|  | // This class provides a read-only iterator over items in the ExpiringCache | 
|  | class Iterator { | 
|  | public: | 
|  | explicit Iterator(const ExpiringCache& cache) | 
|  | : cache_(cache), | 
|  | it_(cache_.entries_.begin()) { | 
|  | } | 
|  | ~Iterator() {} | 
|  |  | 
|  | bool HasNext() const { return it_ != cache_.entries_.end(); } | 
|  | void Advance() { ++it_; } | 
|  |  | 
|  | const KeyType& key() const { return it_->first; } | 
|  | const ValueType& value() const { return it_->second.first; } | 
|  | const ExpirationType& expiration() const { return it_->second.second; } | 
|  |  | 
|  | private: | 
|  | const ExpiringCache& cache_; | 
|  |  | 
|  | // Use a second layer of type indirection, as both EntryMap and | 
|  | // EntryMap::const_iterator are dependent types. | 
|  | typedef typename ExpiringCache::EntryMap EntryMap; | 
|  | typename EntryMap::const_iterator it_; | 
|  | }; | 
|  |  | 
|  |  | 
|  | // Constructs an ExpiringCache that stores up to |max_entries|. | 
|  | explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {} | 
|  | ~ExpiringCache() {} | 
|  |  | 
|  | // Returns the value matching |key|, which must be valid at the time |now|. | 
|  | // Returns NULL if the item is not found or has expired. If the item has | 
|  | // expired, it is immediately removed from the cache. | 
|  | // Note: The returned pointer remains owned by the ExpiringCache and is | 
|  | // invalidated by a call to a non-const method. | 
|  | const ValueType* Get(const KeyType& key, const ExpirationType& now) { | 
|  | typename EntryMap::iterator it = entries_.find(key); | 
|  | if (it == entries_.end()) | 
|  | return NULL; | 
|  |  | 
|  | // Immediately remove expired entries. | 
|  | if (!expiration_comp_(now, it->second.second)) { | 
|  | Evict(it, now, true); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return &it->second.first; | 
|  | } | 
|  |  | 
|  | // Updates or replaces the value associated with |key|. | 
|  | void Put(const KeyType& key, | 
|  | const ValueType& value, | 
|  | const ExpirationType& now, | 
|  | const ExpirationType& expiration) { | 
|  | typename EntryMap::iterator it = entries_.find(key); | 
|  | if (it == entries_.end()) { | 
|  | // Compact the cache if it grew beyond the limit. | 
|  | if (entries_.size() == max_entries_ ) | 
|  | Compact(now); | 
|  |  | 
|  | // No existing entry. Creating a new one. | 
|  | entries_.insert(std::make_pair(key, Entry(value, expiration))); | 
|  | } else { | 
|  | // Update an existing cache entry. | 
|  | it->second.first = value; | 
|  | it->second.second = expiration; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Empties the cache. | 
|  | void Clear() { | 
|  | entries_.clear(); | 
|  | } | 
|  |  | 
|  | // Returns the number of entries in the cache. | 
|  | size_t size() const { return entries_.size(); } | 
|  |  | 
|  | // Returns the maximum number of entries in the cache. | 
|  | size_t max_entries() const { return max_entries_; } | 
|  |  | 
|  | bool empty() const { return entries_.empty(); } | 
|  |  | 
|  | private: | 
|  | FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, Compact); | 
|  | FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor); | 
|  |  | 
|  | // Prunes entries from the cache to bring it below |max_entries()|. | 
|  | void Compact(const ExpirationType& now) { | 
|  | // Clear out expired entries. | 
|  | typename EntryMap::iterator it; | 
|  | for (it = entries_.begin(); it != entries_.end(); ) { | 
|  | if (!expiration_comp_(now, it->second.second)) { | 
|  | Evict(it++, now, false); | 
|  | } else { | 
|  | ++it; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (entries_.size() < max_entries_) | 
|  | return; | 
|  |  | 
|  | // If the cache is still too full, start deleting items 'randomly'. | 
|  | for (it = entries_.begin(); | 
|  | it != entries_.end() && entries_.size() >= max_entries_;) { | 
|  | Evict(it++, now, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Evict(typename EntryMap::iterator it, | 
|  | const ExpirationType& now, | 
|  | bool on_get) { | 
|  | eviction_handler_.Handle(it->first, it->second.first, it->second.second, | 
|  | now, on_get); | 
|  | entries_.erase(it); | 
|  | } | 
|  |  | 
|  | // Bound on total size of the cache. | 
|  | size_t max_entries_; | 
|  |  | 
|  | EntryMap entries_; | 
|  | ExpirationCompare expiration_comp_; | 
|  | EvictionHandler eviction_handler_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(ExpiringCache); | 
|  | }; | 
|  |  | 
|  | }  // namespace net | 
|  |  | 
|  | #endif  // NET_BASE_EXPIRING_CACHE_H_ |