// bdlcc_cache.h                                                      -*-C++-*-
#ifndef INCLUDED_BDLCC_CACHE
#define INCLUDED_BDLCC_CACHE

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a in-process cache with configurable eviction policy.
//
//@CLASSES:
//  bdlcc::Cache: in-process key-value cache
//
//@DESCRIPTION: This component defines a single class template, 'bdlcc::Cache',
// implementing a thread-safe in-memory key-value cache with a configurable
// eviction policy.
//
// 'bdlcc::Cache' class uses similar template parameters to
// 'bsl::unordered_map': the key type ('KEY'), the value type ('VALUE'), the
// optional hash function ('HASH'), and the optional equal function ('EQUAL').
// 'bdlcc::Cache' does not support the standard allocator template parameter
// (although 'bslma::Allocator' is supported).
//
// The cache size can be controlled by setting the low watermark and high
// watermark attributes, which is used instead of a single maximum size
// attribute for performance benefits.  Eviction of cached items starts when
// 'size() >= highWatermark' and continues until 'size() < lowWatermark'.  A
// fixed maximum size is obtained by setting the high and low watermarks to the
// same value.
//
// Two eviction policies are supported: LRU (Least Recently Used) and FIFO
// (First In, First Out).  With LRU, the item that has *not* been accessed for
// the longest period of time will be evicted first.  With FIFO, the eviction
// order is based on the order of insertion, with the earliest inserted item
// being evicted first.
//
///Thread Safety
///-------------
// The 'bdlcc::Cache' class template is fully thread-safe (see
// 'bsldoc_glossary') provided that the allocator supplied at construction and
// the default allocator in effect during the lifetime of cached items are both
// fully thread-safe.  The thread-safety of the container does not extend to
// thread-safety of the contained objects.  Thread-safety for the contained
// objects, if needed, must be arranged by the user separately.
//
///Thread Contention
///-----------------
// Threads accessing a 'bdlcc::Cache' may block while waiting for other threads
// to complete their operations upon the cache.  Concurrent reading is
// supported.  Neither readers or writers are starved by the other group.
//
// All of the modifier methods of the cache potentially requires a write lock.
// Of particular note is the 'tryGetValue' method, which requires a writer lock
// only if the eviction queue needs to be modified.  This means 'tryGetValue'
// requires only a read lock if the eviction policy is set to FIFO or the
// argument 'modifyEvictionQueue' is set to 'false'.  For limited cases where
// contention is likely, temporarily setting 'modifyEvictionQueue' to 'false'
// might be of value.
//
// The 'visit' method acquires a read lock and calls the supplied visitor
// function for every item in the cache, or until the visitor function returns
// 'false'.  If the supplied visitor is expensive or the cache is very large,
// calls to modifier methods might be starved until the 'visit' method finishes
// looping through the cache items.  Therefore, the 'visit' method should be
// used judiciously by making the method call relatively cheap or ensuring that
// no time-sensitive write operation is done at the same time as a call to the
// 'visit' method.  A 'visit' method call is inexpensive if the visitor returns
// quickly, or if the visitor returns false after only a subset of the cache
// items were processed.
//
///Post-eviction Callback and Potential Deadlocks
///---------------------------------------------
// When an item is evicted or erased from the cache, the previously set
// post-eviction callback (via the 'setPostEvictionCallback' method) will be
// invoked within the calling thread, supplying a pointer to the item being
// removed.
//
// The cache object itself should not be used in a post-eviction callback;
// otherwise, a deadlock may result.  Since a write lock is held during the
// call to the callback, invoking any operation on the cache that acquires a
// lock inside the callback will lead to a deadlock.
//
///Runtime Complexity
///------------------
//..
// +----------------------------------------------------+--------------------+
// | Operation                                          | Complexity         |
// +====================================================+====================+
// | insert                                             | Average: O[1]      |
// |                                                    | Worst:   O[n]      |
// +----------------------------------------------------+--------------------+
// | tryGetValue                                        | Average: O[1]      |
// |                                                    | Worst:   O[n]      |
// +----------------------------------------------------+--------------------+
// | popFront                                           | O[1]               |
// +----------------------------------------------------+--------------------+
// | erase                                              | Average: O[1]      |
// |                                                    | Worst:   O[n]      |
// +----------------------------------------------------+--------------------+
// | visit                                              | O[n]               |
// +----------------------------------------------------+--------------------+
//..
//
///Usage
///-----
// In this section we show intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// This examples shows some basic usage of the cache.  First, we define a
// custom post-eviction callback function, 'myPostEvictionCallback' that simply
// prints the evicted item to stdout:
//..
//  void myPostEvictionCallback(bsl::shared_ptr<bsl::string> value)
//  {
//      bsl::cout << "Evicted: " << *value << bsl::endl;
//  }
//..
// Then, we define a 'bdlcc::Cache' object, 'myCache', that maps 'int' to
// 'bsl::string' and uses the LRU eviction policy:
//..
//  bdlcc::Cache<int, bsl::string>
//      myCache(bdlcc::CacheEvictionPolicy::e_LRU, 6, 7, &talloc);
//..
// Next, we insert 3 items into the cache and verify that the size of the cache
// has been updated correctly:
//..
//  myCache.insert(0, "Alex");
//  myCache.insert(1, "John");
//  myCache.insert(2, "Rob");
//  assert(myCache.size() == 3);
//..
// Then, we bulk insert 3 additional items into the cache and verify that the
// size of the cache has been updated correctly:
//..
//  typedef bsl::pair<int, bsl::shared_ptr<bsl::string> > PairType;
//  bsl::vector<PairType> insertData(&talloc);
//  insertData.push_back(PairType(3,
//                        bsl::allocate_shared<bsl::string>(&talloc, "Jim" )));
//  insertData.push_back(PairType(4,
//                        bsl::allocate_shared<bsl::string>(&talloc, "Jeff")));
//  insertData.push_back(PairType(5,
//                        bsl::allocate_shared<bsl::string>(&talloc, "Ian" )));
//  myCache.insertBulk(insertData);
//  assert(myCache.size() == 6);
//..
// Next, we retrieve the second value of the second item stored in the cache
// using the 'tryGetValue' method:
//..
//  bsl::shared_ptr<bsl::string> value;
//  int rc = myCache.tryGetValue(&value, 1);
//  assert(rc == 0);
//  assert(*value == "John");
//..
// Then, we set the cache's post-eviction callback to 'myPostEvictionCallback':
//..
//  myCache.setPostEvictionCallback(myPostEvictionCallback);
//..
// Now, we insert two more items into the cache to trigger the eviction
// behavior:
//..
//  myCache.insert(6, "Steve");
//  assert(myCache.size() == 7);
//  myCache.insert(7, "Tim");
//  assert(myCache.size() == 6);
//..
// Notice that after we insert "Steve", the size of the cache is 7, the high
// watermark.  After the following item, "Tim", is inserted, the size of the
// cache goes back down to 6, the low watermark.
//
// Finally, we observe the following output to stdout:
//..
//  Evicted: Alex
//  Evicted: Rob
//..
// Notice that the item "John" was not evicted even though it was inserted
// before "Rob", because "John" was accessed after "Rob" was inserted.
//
///Example 2: Updating Cache in The Background
///- - - - - - - - - - - - - - - - - - - - - -
// Suppose that a service needs to retrieve some values that are relatively
// expensive to compute.  Clients of the service cannot wait for computing the
// values, so the service should pre-compute and cache them.  In addition, the
// values are only valid for around one hour, so older items must be
// periodically updated in the cache.  This problem can be solved using
// 'bdlcc::Cache' with a background updater thread.
//
// First, we define the types representing the cached values and the cache
// itself:
//..
//  struct MyValue {
//      int            d_data;       // data
//      bdlt::Datetime d_timestamp;  // last update time stamp
//  };
//  typedef bdlcc::Cache<int, MyValue> MyCache;
//..
// Then, suppose that we have access to a function 'retrieveValue' that returns
// a 'MyValue' object given a 'int' key:
//..
//  MyValue retrieveValue(int key)
//  {
//      MyValue ret = {key, bdlt::CurrentTime::utc()};
//      return ret;
//  }
//..
// Next, we define a visitor type to aggregate keys of the out-of-date values
// in the cache:
//..
//  struct MyVisitor {
//      // Visitor to 'MyCache'.
//      bsl::vector<int>  d_oldKeys;  // list of out-of-date keys
//
//      MyVisitor()
//      : d_oldKeys(&talloc)
//      {}
//
//      bool operator() (int key, const MyValue& value)
//        // Check if the specified 'value' is older than 1 hour.  If so,
//        // insert the specified 'key' into 'd_oldKeys'.
//      {
//          if (veryVerbose) {
//              bsl::cout << "Visiting " << key
//                        << ", age: "
//                        << bdlt::CurrentTime::utc() - value.d_timestamp
//                        << bsl::endl;
//          }
//
//          if (bdlt::CurrentTime::utc() - value.d_timestamp <
//              // bdlt::DatetimeInterval(0, 60)) {
//              bdlt::DatetimeInterval(0, 0, 0, 3)) {
//              return false;                                         // RETURN
//          }
//
//          d_oldKeys.push_back(key);
//          return true;
//      }
//  };
//..
// Then, we define the background thread function to find and update the
// out-of-date values:
//..
//  void myWorker(MyCache *cache)
//  {
//      while (true) {
//          if (cache->size() == 0) {
//              break;
//          }
//
//          // Find and update the old values once per minute.
//          // bslmt::ThreadUtil::microSleep(0, 60);
//          bslmt::ThreadUtil::microSleep(0, 5);
//          MyVisitor visitor;
//          cache->visit(visitor);
//          for (bsl::vector<int>::const_iterator itr =
//               visitor.d_oldKeys.begin();
//               itr != visitor.d_oldKeys.end(); ++itr) {
//              if (veryVerbose) bsl::cout << "Updating " << *itr << bsl::endl;
//              cache->insert(*itr, retrieveValue(*itr));
//          }
//      }
//  }
//
//  extern "C" void *myWorkerThread(void *v_cache)
//  {
//      MyCache *cache = (MyCache *) v_cache;
//      myWorker(cache);
//      return 0;
//  }
//..
// Finally, we define the entry point of the application:
//..
//  void example2()
//  {
//      MyCache myCache(bdlcc::CacheEvictionPolicy::e_FIFO, 100, 120, &talloc);
//
//      // Pre-populate the cache.
//
//      myCache.insert(0, retrieveValue(0));
//      myCache.insert(1, retrieveValue(1));
//      myCache.insert(2, retrieveValue(2));
//      assert(myCache.size() == 3);
//
//      bslmt::ThreadUtil::Handle myWorkerHandle;
//
//      int rc = bslmt::ThreadUtil::create(&myWorkerHandle, myWorkerThread,
//                                         &myCache);
//      assert(rc == 0);
//
//      // Do some work.
//
//      bslmt::ThreadUtil::microSleep(0, 7);
//      assert(myCache.size() == 3);
//
//      // Clean up.
//
//      myCache.clear();
//      assert(myCache.size() == 0);
//      bslmt::ThreadUtil::join(myWorkerHandle);
//  }
//..

#include <bslim_printer.h>

#include <bslmt_readerwritermutex.h>
#include <bslmt_readlockguard.h>
#include <bslmt_writelockguard.h>

#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_allocatorargt.h>
#include <bslmf_assert.h>
#include <bslmf_integralconstant.h>
#include <bslmf_movableref.h>

#include <bsls_assert.h>
#include <bsls_libraryfeatures.h>
#include <bsls_review.h>

#include <bsl_memory.h>
#include <bsl_map.h>
#include <bsl_unordered_map.h>
#include <bsl_list.h>
#include <bsl_vector.h>
#include <bsl_functional.h>
#include <bsl_iostream.h>
#include <bsl_limits.h>
#include <bsl_cstddef.h>            // 'bsl::size_t'

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
# include <memory_resource>
#endif

namespace BloombergLP {
namespace bdlcc {

struct CacheEvictionPolicy {

    // TYPES
    enum Enum {
        // Enumeration of supported cache eviction policies.

        e_LRU,  // Least Recently Used
        e_FIFO  // First In, First Out
    };
};

template <class KEY>
class Cache_QueueProctor {
    // This class implements a proctor that, on destruction, restores the queue
    // to its state at the time of the proctor's creation.  We assume that the
    // only change to the queue is that 0 or more items have been added to the
    // end.  If 'release' has been called, the destructor takes no action.

    // DATA
    bsl::list<KEY>   *d_queue_p;  // queue (held, not owned)
    KEY              *d_last_p;

  private:
    // PRIVATE ACCESSORS
    KEY *last() const;
        // Return a pointer to the element at the end of the queue, or 0 if
        // the queue is empty.

  public:
    // CREATORS
    explicit Cache_QueueProctor(bsl::list<KEY> *queue);
        // Create a 'Cache_QueueProctor' object to monitor the specified
        // 'queue'.

    ~Cache_QueueProctor();
        // Destroy this proctor object.  Remove any elements that we added
        // since the proctor was created.

    // MANIPULATORS
    void release();
        // Release the queue specified on construction, so that it will not be
        // modified on the destruction of this proctor.
};

template <class KEY,
          class VALUE,
          class HASH  = bsl::hash<KEY>,
          class EQUAL = bsl::equal_to<KEY> >
class Cache_TestUtil;

template <class KEY,
          class VALUE,
          class HASH  = bsl::hash<KEY>,
          class EQUAL = bsl::equal_to<KEY> >
class Cache {
    // This class represents a simple in-process key-value store supporting a
    // variety of eviction policies.

  public:
    // PUBLIC TYPES
    typedef bsl::shared_ptr<VALUE>                            ValuePtrType;
        // Shared pointer type pointing to value type.

    typedef bsl::function<void(const ValuePtrType&)> PostEvictionCallback;
        // Type of function to call after an item has been evicted from the
        // cache.

    typedef bsl::pair<KEY, ValuePtrType>                          KVType;
        // Value type of a bulk insert entry.

  private:
    // PRIVATE TYPES
    typedef bsl::list<KEY>                                        QueueType;
        // Eviction queue type.

    typedef bsl::pair<ValuePtrType, typename QueueType::iterator> MapValue;
        // Value type of the hash map.

    typedef bsl::unordered_map<KEY, MapValue, HASH, EQUAL>        MapType;
        // Hash map type.

    typedef bslmt::ReaderWriterMutex                              LockType;

    // DATA
    bslma::Allocator          *d_allocator_p;          // memory allocator
                                                       // (held, not owned)

    mutable LockType           d_rwlock;               // reader-writer lock

    MapType                    d_map;                  // hash table storing
                                                       // key-value pairs

    QueueType                  d_queue;                // queue storing
                                                       // eviction order of
                                                       // keys, the key of the
                                                       // first item to be
                                                       // evicted is at the
                                                       // front of the queue

    CacheEvictionPolicy::Enum  d_evictionPolicy;       // eviction policy

    bsl::size_t                d_lowWatermark;         // the size of this
                                                       // cache when eviction
                                                       // stops

    bsl::size_t                d_highWatermark;        // the size of this
                                                       // cache when eviction
                                                       // starts after an
                                                       // insert

    PostEvictionCallback       d_postEvictionCallback; // the function to call
                                                       // after a value has
                                                       // been evicted from the
                                                       // cache

    // FRIENDS
    friend class Cache_TestUtil<KEY, VALUE, HASH, EQUAL>;

    // PRIVATE MANIPULATORS
    void enforceHighWatermark();
        // Evict items from this cache if 'size() >= highWatermark()' until
        // 'size() < lowWatermark()' beginning from the front of the eviction
        // queue.  Invoke the post-eviction callback for each item evicted.

    void evictItem(const typename MapType::iterator& mapIt);
        // Evict the item at the specified 'mapIt' and invoke the post-eviction
        // callback for that item.

    bool insertValuePtrMoveImp(KEY          *key_p,
                               bool          moveKey,
                               ValuePtrType *valuePtr_p,
                               bool          moveValuePtr);
        // Add a node with the specified '*key_p' and the specified
        // '*valuePtr_p' to the cache.  If an entry already exists for
        // '*key_p', override its value with '*valuePtr_p'.  If the specified
        // 'moveKey' is 'true', move '*key_p', and if the specified
        // 'moveValuePtr' is 'true', move '*valuePtr_p', if the boolean values
        // corresponding to '*key_p' or '*valuePtr_p' are 'false', do not move
        // or modify the arguments.  Return 'true' if '*key_p' was not
        // previously in the cache and 'false' otherwise.

    void populateValuePtrType(ValuePtrType             *dst,
                              const VALUE&              value,
                              bsl::true_type);
    void populateValuePtrType(ValuePtrType             *dst,
                              const VALUE&              value,
                              bsl::false_type);
    void populateValuePtrType(ValuePtrType             *dst,
                              bslmf::MovableRef<VALUE>  value,
                              bsl::true_type);
    void populateValuePtrType(ValuePtrType             *dst,
                              bslmf::MovableRef<VALUE>  value,
                              bsl::false_type);
        // Allocate a footprint for the specified 'value', copy or move 'value'
        // into the footprint and load the specified '*dst' with a pointer to
        // the value.

  private:
    // NOT IMPLEMENTED
    Cache(const Cache<KEY, VALUE, HASH, EQUAL>&);

    // BDE_VERIFY pragma: -FD01
    Cache<KEY, VALUE, HASH, EQUAL>& operator=(const Cache<KEY, VALUE, HASH>&);
    // BDE_VERIFY pragma: +FD01

  public:
    // CREATORS
    explicit Cache(bslma::Allocator *basicAllocator = 0);
        // Create an empty LRU cache having no size limit.  Optionally specify
        // a 'basicAllocator' used to supply memory.  If 'basicAllocator' is 0,
        // the currently installed default allocator is used.

    Cache(CacheEvictionPolicy::Enum  evictionPolicy,
          bsl::size_t                lowWatermark,
          bsl::size_t                highWatermark,
          bslma::Allocator          *basicAllocator = 0);
        // Create an empty cache using the specified 'evictionPolicy' and the
        // specified 'lowWatermark' and 'highWatermark'.  Optionally specify
        // the 'basicAllocator' used to supply memory.  If 'basicAllocator' is
        // 0, the currently installed default allocator is used.  The behavior
        // is undefined unless 'lowWatermark <= highWatermark',
        // '1 <= lowWatermark', and '1 <= highWatermark'.

    Cache(CacheEvictionPolicy::Enum  evictionPolicy,
          bsl::size_t                lowWatermark,
          bsl::size_t                highWatermark,
          const HASH&                hashFunction,
          const EQUAL&               equalFunction,
          bslma::Allocator          *basicAllocator = 0);
        // Create an empty cache using the specified 'evictionPolicy',
        // 'lowWatermark', and 'highWatermark'.  The specified 'hashFunction'
        // is used to generate the hash values for a given key, and the
        // specified 'equalFunction' is used to determine whether two keys have
        // the same value.  Optionally specify the 'basicAllocator' used to
        // supply memory.  If 'basicAllocator' is 0, the currently installed
        // default allocator is used.  The behavior is undefined unless
        // 'lowWatermark <= highWatermark', '1 <= lowWatermark', and
        // '1 <= highWatermark'.

    //! ~Cache() = default;
        // Destroy this object.

    // MANIPULATORS
    void clear();
        // Remove all items from this cache.  Do *not* invoke the post-eviction
        // callback.

    int erase(const KEY& key);
        // Remove the item having the specified 'key' from this cache.  Invoke
        // the post-eviction callback for the removed item.  Return 0 on
        // success and 1 if 'key' does not exist.

    template <class INPUT_ITERATOR>
    int eraseBulk(INPUT_ITERATOR begin, INPUT_ITERATOR end);
        // Remove the items having the keys in the specified range
        // '[ begin, end )', from this cache.  Invoke the post-eviction
        // callback for each removed item.  Return the number of items
        // successfully removed.

    int eraseBulk(const bsl::vector<KEY>& keys);
        // Remove the items having the specified 'keys' from this cache.
        // Invoke the post-eviction callback for each removed item.  Return the
        // number of items successfully removed.

    void insert(const KEY& key, const VALUE& value);
    void insert(const KEY& key, bslmf::MovableRef<VALUE> value);
    void insert(bslmf::MovableRef<KEY> key, const VALUE& value);
    void insert(bslmf::MovableRef<KEY> key, bslmf::MovableRef<VALUE> value);
        // Move the specified 'key' and its associated 'value' into this cache.
        // If 'key' already exists, then its value will be replaced with
        // 'value'.  Note that all the methods that take moved objects provide
        // the 'basic' but not the 'strong' exception guarantee -- throws may
        // occur after the objects are moved out of; the cache will not be
        // modified, but 'key' or 'value' may be changed.  Also note that 'key'
        // must be copyable, even if it is moved.

    void insert(const KEY& key, const ValuePtrType& valuePtr);
    void insert(bslmf::MovableRef<KEY> key, const ValuePtrType& valuePtr);
        // Insert the specified 'key' and its associated 'valuePtr' into this
        // cache.  If 'key' already exists, then its value will be replaced
        // with 'value'.  Note that the method with 'key' moved provides the
        // 'basic' but not the 'strong' exception guarantee -- if a throw
        // occurs, the cache will not be modified, but 'key' may be changed.
        // Also note that 'key' must be copyable, even if it is moved.

    template <class INPUT_ITERATOR>
    int insertBulk(INPUT_ITERATOR begin, INPUT_ITERATOR end);
        // Insert the specified range of Key-Value pairs specified by
        // '[ begin, end )' into this cache.  If a key already exists, then its
        // value will be replaced with the value.  Return the number of items
        // successfully inserted.

    int insertBulk(const bsl::vector<KVType>& data);
        // Insert the specified 'data' (composed of Key-Value pairs) into this
        // cache.  If a key already exists, then its value will be replaced
        // with the value.  Return the number of items successfully inserted.

    int insertBulk(bslmf::MovableRef<bsl::vector<KVType> > data);
        // Insert the specified 'data' (composed of Key-Value pairs) into this
        // cache.  If a key already exists, then its value will be replaced
        // with the value.  Return the number of items successfully inserted.
        // If an exception occurs during this action, we provide only the
        // basic guarantee - both this cache and 'data' will be in some valid
        // but unspecified state.

    int popFront();
        // Remove the item at the front of the eviction queue.  Invoke the
        // post-eviction callback for the removed item.  Return 0 on success,
        // and 1 if this cache is empty.

    void setPostEvictionCallback(
                             const PostEvictionCallback& postEvictionCallback);
        // Set the post-eviction callback to the specified
        // 'postEvictionCallback'.  The post-eviction callback is invoked for
        // each item evicted or removed from this cache.

    int tryGetValue(bsl::shared_ptr<VALUE> *value,
                    const KEY&              key,
                    bool                    modifyEvictionQueue = true);
        // Load, into the specified 'value', the value associated with the
        // specified 'key' in this cache.  If the optionally specified
        // 'modifyEvictionQueue' is 'true' and the eviction policy is LRU, then
        // move the cached item to the back of the eviction queue.  Return 0 on
        // success, and 1 if 'key' does not exist in this cache.  Note that a
        // write lock is acquired only if this queue is modified.

    // ACCESSORS
    EQUAL equalFunction() const;
        // Return (a copy of) the key-equality functor used by this cache that
        // returns 'true' if two 'KEY' objects have the same value, and 'false'
        // otherwise.

    CacheEvictionPolicy::Enum evictionPolicy() const;
        // Return the eviction policy used by this cache.

    HASH hashFunction() const;
        // Return (a copy of) the unary hash functor used by this cache to
        // generate a hash value (of type 'std::size_t') for a 'KEY' object.

    bsl::size_t highWatermark() const;
        // Return the high watermark of this cache, which is the size at which
        // eviction of existing items begins.

    bsl::size_t lowWatermark() const;
        // Return the low watermark of this cache, which is the size at which
        // eviction of existing items ends.

    bsl::size_t size() const;
        // Return the current size of this cache.

    template <class VISITOR>
    void visit(VISITOR& visitor) const;
        // Call the specified 'visitor' for every item stored in this cache in
        // the order of the eviction queue until 'visitor' returns 'false'.
        // The 'VISITOR' type must be a callable object that can be invoked in
        // the same way as the function 'bool (const KEY&, const VALUE&)'
};

template <class KEY,
          class VALUE,
          class HASH,
          class EQUAL>
class Cache_TestUtil {
    // This class implements a test utility that gives the test driver access
    // to the lock / unlock method of the RW mutex.  Its purpose is to allow
    // testing that the locking actually happens as planned.

    // DATA
    Cache<KEY, VALUE, HASH, EQUAL>& d_cache;

  public:
    // CREATORS
    explicit Cache_TestUtil(Cache<KEY, VALUE, HASH, EQUAL>& cache);
        // Create a 'Cache_TestUtil' object to test locking in the specified
        // 'cache'.

    //! ~Cache_TestUtil() = default;
        // Destroy this object.

    // MANIPULATORS
    void lockRead();
        // Call the 'lockRead' method of 'bdlcc::Cache' 'd_rwlock' lock.

    void lockWrite();
        // Call the 'lockWrite' method of 'bdlcc::Cache' 'd_rwlock' lock.

    void unlock();
        // Call the 'unlock' method of 'bdlcc::Cache' 'd_rwlock' lock.

};

// ============================================================================
//                        INLINE FUNCTION DEFINITIONS
// ============================================================================

                        // ------------------------
                        // class Cache_QueueProctor
                        // ------------------------

// PRIVATE ACCESSORS
template <class KEY>
inline
KEY *Cache_QueueProctor<KEY>::last() const
{
    return !d_queue_p || d_queue_p->empty() ? 0
                                            : &*d_queue_p->rbegin();
}

// CREATORS
template <class KEY>
inline
Cache_QueueProctor<KEY>::Cache_QueueProctor(bsl::list<KEY> *queue)
: d_queue_p(queue)
, d_last_p(last())
{}

template <class KEY>
inline
Cache_QueueProctor<KEY>::~Cache_QueueProctor()
{
    if (d_queue_p) {
        while (last() != d_last_p) {
            d_queue_p->pop_back();
        }
    }
}

// MANIPULATORS
template <class KEY>
inline
void Cache_QueueProctor<KEY>::release()
{
    d_queue_p = 0;
}

                        // -----------
                        // class Cache
                        // -----------

// CREATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
Cache<KEY, VALUE, HASH, EQUAL>::Cache(bslma::Allocator *basicAllocator)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
, d_map(d_allocator_p)
, d_queue(d_allocator_p)
, d_evictionPolicy(CacheEvictionPolicy::e_LRU)
, d_lowWatermark(bsl::numeric_limits<bsl::size_t>::max())
, d_highWatermark(bsl::numeric_limits<bsl::size_t>::max())
, d_postEvictionCallback(bsl::allocator_arg, d_allocator_p)
{
}

template <class KEY, class VALUE, class HASH, class EQUAL>
Cache<KEY, VALUE, HASH, EQUAL>::Cache(
                                     CacheEvictionPolicy::Enum  evictionPolicy,
                                     bsl::size_t                lowWatermark,
                                     bsl::size_t                highWatermark,
                                     bslma::Allocator          *basicAllocator)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
, d_map(d_allocator_p)
, d_queue(d_allocator_p)
, d_evictionPolicy(evictionPolicy)
, d_lowWatermark(lowWatermark)
, d_highWatermark(highWatermark)
, d_postEvictionCallback(bsl::allocator_arg, d_allocator_p)
{
    BSLS_REVIEW(lowWatermark <= highWatermark);
    BSLS_REVIEW(1 <= lowWatermark);
    BSLS_REVIEW(1 <= highWatermark);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
Cache<KEY, VALUE, HASH, EQUAL>::Cache(
                                     CacheEvictionPolicy::Enum  evictionPolicy,
                                     bsl::size_t                lowWatermark,
                                     bsl::size_t                highWatermark,
                                     const HASH&                hashFunction,
                                     const EQUAL&               equalFunction,
                                     bslma::Allocator          *basicAllocator)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
, d_map(0, hashFunction, equalFunction, d_allocator_p)
, d_queue(d_allocator_p)
, d_evictionPolicy(evictionPolicy)
, d_lowWatermark(lowWatermark)
, d_highWatermark(highWatermark)
, d_postEvictionCallback(bsl::allocator_arg, d_allocator_p)
{
    BSLS_REVIEW(lowWatermark <= highWatermark);
    BSLS_REVIEW(1 <= lowWatermark);
    BSLS_REVIEW(1 <= highWatermark);
}

// PRIVATE MANIPULATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::enforceHighWatermark()
{
    if (d_map.size() < d_highWatermark) {
        return;                                                       // RETURN
    }

    while (d_map.size() >= d_lowWatermark && d_map.size() > 0) {
        const typename MapType::iterator mapIt = d_map.find(d_queue.front());
        BSLS_ASSERT(mapIt != d_map.end());
        evictItem(mapIt);
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::evictItem(
                                       const typename MapType::iterator& mapIt)
{
    ValuePtrType value = mapIt->second.first;

    d_queue.erase(mapIt->second.second);
    d_map.erase(mapIt);

    if (d_postEvictionCallback) {
        d_postEvictionCallback(value);
    }
}
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bool Cache<KEY, VALUE, HASH, EQUAL>::insertValuePtrMoveImp(
                                                    KEY          *key_p,
                                                    bool          moveKey,
                                                    ValuePtrType *valuePtr_p,
                                                    bool          moveValuePtr)
{
#if defined(BSLMF_MOVABLEREF_USES_RVALUE_REFERENCES)
    enum { k_RVALUE_ASSIGN = true };
#else
    enum { k_RVALUE_ASSIGN = false };
#endif

    enforceHighWatermark();

    KEY&          key      = *key_p;
    ValuePtrType& valuePtr = *valuePtr_p;

    typename MapType::iterator mapIt = d_map.find(key);
    if (mapIt != d_map.end()) {
        if (k_RVALUE_ASSIGN && moveValuePtr) {
            mapIt->second.first = bslmf::MovableRefUtil::move(valuePtr);
        }
        else {
            mapIt->second.first = valuePtr;
        }

        typename QueueType::iterator queueIt = mapIt->second.second;

        // Move 'queueIt' to the back of 'd_queue'.

        d_queue.splice(d_queue.end(), d_queue, queueIt);

        return false;                                                 // RETURN
    }
    else {
        Cache_QueueProctor<KEY>      proctor(&d_queue);
        d_queue.push_back(key);
        typename QueueType::iterator queueIt = d_queue.end();
        --queueIt;

        bsls::ObjectBuffer<MapValue> mapValueFootprint;
        MapValue *mapValue_p = mapValueFootprint.address();

        if (moveValuePtr) {
            new (mapValue_p) MapValue(bslmf::MovableRefUtil::move(valuePtr),
                                      queueIt,
                                      d_allocator_p);
        }
        else {
            new (mapValue_p) MapValue(valuePtr,
                                      queueIt,
                                      d_allocator_p);
        }
        bslma::DestructorGuard<MapValue> mapValueGuard(mapValue_p);

        if (moveKey) {
            d_map.emplace(bslmf::MovableRefUtil::move(key),
                          bslmf::MovableRefUtil::move(*mapValue_p));
        }
        else {
            d_map.emplace(key,
                          bslmf::MovableRefUtil::move(*mapValue_p));
        }

        proctor.release();

        return true;                                                  // RETURN
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache<KEY, VALUE, HASH, EQUAL>::populateValuePtrType(ValuePtrType *dst,
                                                          const VALUE&  value,
                                                          bsl::true_type)
{
    dst->createInplace(d_allocator_p, value, d_allocator_p);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache<KEY, VALUE, HASH, EQUAL>::populateValuePtrType(ValuePtrType *dst,
                                                          const VALUE&  value,
                                                          bsl::false_type)
{
    dst->createInplace(d_allocator_p, value);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache<KEY, VALUE, HASH, EQUAL>::populateValuePtrType(
                                               ValuePtrType             *dst,
                                               bslmf::MovableRef<VALUE>  value,
                                               bsl::true_type)
{
    dst->createInplace(d_allocator_p,
                       bslmf::MovableRefUtil::move(value),
                       d_allocator_p);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache<KEY, VALUE, HASH, EQUAL>::populateValuePtrType(
                                               ValuePtrType             *dst,
                                               bslmf::MovableRef<VALUE>  value,
                                               bsl::false_type)
{
    dst->createInplace(d_allocator_p,
                       bslmf::MovableRefUtil::move(value));
}

// MANIPULATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::clear()
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);
    d_map.clear();
    d_queue.clear();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
int Cache<KEY, VALUE, HASH, EQUAL>::erase(const KEY& key)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    const typename MapType::iterator mapIt = d_map.find(key);
    if (mapIt == d_map.end()) {
        return 1;                                                     // RETURN
    }

    evictItem(mapIt);
    return 0;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class INPUT_ITERATOR>
int Cache<KEY, VALUE, HASH, EQUAL>::eraseBulk(INPUT_ITERATOR begin,
                                              INPUT_ITERATOR end)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    int count = 0;
    for (; begin != end; ++begin) {
        const typename MapType::iterator mapIt = d_map.find(*begin);
        if (mapIt == d_map.end()) {
            continue;
        }
        ++count;
        evictItem(mapIt);
    }

    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int Cache<KEY, VALUE, HASH, EQUAL>::eraseBulk(const bsl::vector<KEY>& keys)
{
    return eraseBulk(keys.begin(), keys.end());
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache<KEY, VALUE, HASH, EQUAL>::insert(const KEY& key, const VALUE& value)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    KEY          *key_p = const_cast<KEY *>(&key);
    ValuePtrType  valuePtr;
    populateValuePtrType(&valuePtr, value, bslma::UsesBslmaAllocator<VALUE>());
                                                                 // might throw

    insertValuePtrMoveImp(key_p, false, &valuePtr, true);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::insert(const KEY&               key,
                                            bslmf::MovableRef<VALUE> value)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    KEY          *key_p = const_cast<KEY *>(&key);
    ValuePtrType  valuePtr;
    populateValuePtrType(&valuePtr,
                         bslmf::MovableRefUtil::move(value),
                         bslma::UsesBslmaAllocator<VALUE>());
                                    // might throw, but BEFORE 'value' is moved

    insertValuePtrMoveImp(key_p, false, &valuePtr, true);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::insert(bslmf::MovableRef<KEY> key,
                                            const VALUE&           value)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    KEY& localKey = key;

    ValuePtrType valuePtr;
    populateValuePtrType(&valuePtr, value, bslma::UsesBslmaAllocator<VALUE>());
                                                                 // might throw

    insertValuePtrMoveImp(&localKey, true, &valuePtr, true);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::insert(bslmf::MovableRef<KEY>   key,
                                            bslmf::MovableRef<VALUE> value)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    KEY& localKey = key;

    ValuePtrType valuePtr;
    populateValuePtrType(&valuePtr,
                         bslmf::MovableRefUtil::move(value),
                         bslma::UsesBslmaAllocator<VALUE>());
                                    // might throw, but BEFORE 'value' is moved

    insertValuePtrMoveImp(&localKey, true, &valuePtr, true);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache<KEY, VALUE, HASH, EQUAL>::insert(const KEY&          key,
                                            const ValuePtrType& valuePtr)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    KEY          *key_p      = const_cast<KEY *>(&key);
    ValuePtrType *valuePtr_p = const_cast<ValuePtrType *>(&valuePtr);

    insertValuePtrMoveImp(key_p, false, valuePtr_p, false);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::insert(bslmf::MovableRef<KEY> key,
                                            const ValuePtrType&    valuePtr)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    KEY&          localKey = key;
    ValuePtrType *valuePtr_p = const_cast<ValuePtrType *>(&valuePtr);

    insertValuePtrMoveImp(&localKey, true, valuePtr_p, false);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class INPUT_ITERATOR>
int Cache<KEY, VALUE, HASH, EQUAL>::insertBulk(INPUT_ITERATOR begin,
                                               INPUT_ITERATOR end)
{
    int                             count = 0;
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    for (; begin != end; ++begin) {
        KEY          *key_p      = const_cast<KEY *>(         &begin->first);
        ValuePtrType *valuePtr_p = const_cast<ValuePtrType *>(&begin->second);

        count += insertValuePtrMoveImp(key_p, false, valuePtr_p, false);
    }

    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int Cache<KEY, VALUE, HASH, EQUAL>::insertBulk(const bsl::vector<KVType>& data)
{
    return insertBulk(data.begin(), data.end());
}

template <class KEY, class VALUE, class HASH, class EQUAL>
int Cache<KEY, VALUE, HASH, EQUAL>::insertBulk(
                                  bslmf::MovableRef<bsl::vector<KVType> > data)
{
    typedef bsl::vector<KVType> Vec;

    Vec& local = data;

    int                             count = 0;
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    for (typename Vec::iterator it = local.begin(); it < local.end(); ++it) {
        KEY          *key_p      = &it->first;
        ValuePtrType *valuePtr_p = &it->second;

        count += insertValuePtrMoveImp(key_p, true, valuePtr_p, true);
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int Cache<KEY, VALUE, HASH, EQUAL>::popFront()
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);

    if (d_map.size() > 0) {
        const typename MapType::iterator mapIt = d_map.find(d_queue.front());
        BSLS_ASSERT(mapIt != d_map.end());
        evictItem(mapIt);
        return 0;                                                     // RETURN
    }

    return 1;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void Cache<KEY, VALUE, HASH, EQUAL>::setPostEvictionCallback(
                              const PostEvictionCallback& postEvictionCallback)
{
    bslmt::WriteLockGuard<LockType> guard(&d_rwlock);
    d_postEvictionCallback = postEvictionCallback;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
int Cache<KEY, VALUE, HASH, EQUAL>::tryGetValue(
                                   bsl::shared_ptr<VALUE> *value,
                                   const KEY&              key,
                                   bool                    modifyEvictionQueue)
{
    int writeLock = d_evictionPolicy == CacheEvictionPolicy::e_LRU &&
         modifyEvictionQueue ? 1 : 0;
    if (writeLock) {
        d_rwlock.lockWrite();
    }
    else {
        d_rwlock.lockRead();
    }

    // Since the guard is constructed with a locked synchronization object, the
    // guard's call to 'unlock' correctly handles both read and write
    // scenarios.

    bslmt::ReadLockGuard<LockType> guard(&d_rwlock, true);

    typename MapType::iterator mapIt = d_map.find(key);
    if (mapIt == d_map.end()) {
        return 1;                                                     // RETURN
    }

    *value = mapIt->second.first;

    if (writeLock) {
        typename QueueType::iterator queueIt = mapIt->second.second;
        typename QueueType::iterator last = d_queue.end();
        --last;
        if (last != queueIt) {
            d_queue.splice(d_queue.end(), d_queue, queueIt);
        }
    }

    return 0;
}

// ACCESSORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
EQUAL Cache<KEY, VALUE, HASH, EQUAL>::equalFunction() const
{
    return d_map.key_eq();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
CacheEvictionPolicy::Enum
Cache<KEY, VALUE, HASH, EQUAL>::evictionPolicy() const
{
    return d_evictionPolicy;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
HASH Cache<KEY, VALUE, HASH, EQUAL>::hashFunction() const
{
    return d_map.hash_function();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t Cache<KEY, VALUE, HASH, EQUAL>::highWatermark() const
{
    return d_highWatermark;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t Cache<KEY, VALUE, HASH, EQUAL>::lowWatermark() const
{
    return d_lowWatermark;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t Cache<KEY, VALUE, HASH, EQUAL>::size() const
{
    bslmt::ReadLockGuard<LockType> guard(&d_rwlock);
    return d_map.size();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class VISITOR>
void Cache<KEY, VALUE, HASH, EQUAL>::visit(VISITOR& visitor) const
{
    bslmt::ReadLockGuard<LockType> guard(&d_rwlock);

    for (typename QueueType::const_iterator queueIt = d_queue.begin();
         queueIt != d_queue.end(); ++queueIt) {

        const KEY&                             key = *queueIt;
        const typename MapType::const_iterator mapIt = d_map.find(key);
        BSLS_ASSERT(mapIt != d_map.end());
        const ValuePtrType& valuePtr = mapIt->second.first;

        if (!visitor(key, *valuePtr)) {
            break;
        }
    }
}

                            // --------------------
                            // class Cache_TestUtil
                            // --------------------

// CREATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
Cache_TestUtil<KEY, VALUE, HASH, EQUAL>::Cache_TestUtil(
                                         Cache<KEY, VALUE, HASH, EQUAL>& cache)
: d_cache(cache)
{
}

// MANIPULATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache_TestUtil<KEY, VALUE, HASH, EQUAL>::lockRead()
{
    d_cache.d_rwlock.lockRead();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache_TestUtil<KEY, VALUE, HASH, EQUAL>::lockWrite()
{
    d_cache.d_rwlock.lockWrite();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void Cache_TestUtil<KEY, VALUE, HASH, EQUAL>::unlock()
{
    d_cache.d_rwlock.unlock();
}

}  // close package namespace


namespace bslma {

template <class KEY,  class VALUE,  class HASH,  class EQUAL>
struct UsesBslmaAllocator<bdlcc::Cache<KEY, VALUE, HASH, EQUAL> >
    : bsl::true_type
{
};

}  // close namespace bslma

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2017 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------