text: ﻿/*
  ******************************************************************************
  * Copyright (C) 2015, International Business Machines Corporation and
  * others. All Rights Reserved.
  ******************************************************************************
  *
  * File UNIFIEDCACHE.H - The ICU Unified cache.
  ******************************************************************************
  */
  
  #ifndef __UNIFIED_CACHE_H__
  #define __UNIFIED_CACHE_H__
  
  #include "utypeinfo.h"  // for 'typeid' to work
  
  #include "unicode/uobject.h"
  #include "unicode/locid.h"
  #include "sharedobject.h"
  #include "unicode/unistr.h"
  #include "cstring.h"
  #include "ustr_imp.h"
  
  struct UHashtable;
  struct UHashElement;
  
  U_NAMESPACE_BEGIN
  
  class UnifiedCache;
  
  /**
   * A base class for all cache keys.
   */
  class U_COMMON_API CacheKeyBase : public UObject {
   public:
     CacheKeyBase() : fCreationStatus(U_ZERO_ERROR), fIsMaster(FALSE) {}
  
     /**
      * Copy constructor. Needed to support cloning.
      */
     CacheKeyBase(const CacheKeyBase &other) 
             : UObject(other), fCreationStatus(other.fCreationStatus), fIsMaster(FALSE) { }
     virtual ~CacheKeyBase();
  
     /**
      * Returns the hash code for this object.
      */
     virtual int32_t hashCode() const = 0;
  
     /**
      * Clones this object polymorphically. Caller owns returned value.
      */
     virtual CacheKeyBase *clone() const = 0;
  
     /**
      * Equality operator.
      */
     virtual UBool operator == (const CacheKeyBase &other) const = 0;
  
     /**
      * Create a new object for this key. Called by cache on cache miss.
      * createObject must add a reference to the object it returns. Note
      * that getting an object from the cache and returning it without calling
      * removeRef on it satisfies this requirement. It can also return NULL
      * and set status to an error.
      *
      * @param creationContext the context in which the object is being
      *                        created. May be NULL.
      * @param status          Implementations can return a failure here.
      *                        In addition, implementations may return a
      *                        non NULL object and set a warning status.
      */
     virtual const SharedObject *createObject(
             const void *creationContext, UErrorCode &status) const = 0;
  
     /**
      * Writes a description of this key to buffer and returns buffer. Written
      * description is NULL terminated.
      */
     virtual char *writeDescription(char *buffer, int32_t bufSize) const = 0;
  
     /**
      * Inequality operator.
      */
     UBool operator != (const CacheKeyBase &other) const {
         return !(*this == other);
     }
   private:
     mutable UErrorCode fCreationStatus;
     mutable UBool fIsMaster;
     friend class UnifiedCache;
  };
  
  
  
  /**
   * Templated version of CacheKeyBase. 
   * A key of type LocaleCacheKey<T> maps to a value of type T.
   */
  template<typename T>
  class CacheKey : public CacheKeyBase {
   public:
     virtual ~CacheKey() { }
     /**
      * The template parameter, T, determines the hash code returned.
      */
     virtual int32_t hashCode() const {
         const char *s = typeid(T).name();
         return ustr_hashCharsN(s, uprv_strlen(s));
     }
  
     /**
      * Use the value type, T,  as the description.
      */
     virtual char *writeDescription(char *buffer, int32_t bufLen) const {
         const char *s = typeid(T).name();
         uprv_strncpy(buffer, s, bufLen);
         buffer[bufLen - 1] = 0;
         return buffer;
     }
  
     /**
      * Two objects are equal if they are of the same type.
      */
     virtual UBool operator == (const CacheKeyBase &other) const {
         return typeid(*this) == typeid(other);
     }
  };
  
  /**
   * Cache key based on locale.
   * A key of type LocaleCacheKey<T> maps to a value of type T.
   */
  template<typename T>
  class LocaleCacheKey : public CacheKey<T> {
   protected:
     Locale   fLoc;
   public:
     LocaleCacheKey(const Locale &loc) : fLoc(loc) {};
     LocaleCacheKey(const LocaleCacheKey<T> &other)
             : CacheKey<T>(other), fLoc(other.fLoc) { }
     virtual ~LocaleCacheKey() { }
     virtual int32_t hashCode() const {
         return 37 *CacheKey<T>::hashCode() + fLoc.hashCode();
     }
     virtual UBool operator == (const CacheKeyBase &other) const {
         // reflexive
         if (this == &other) {
             return TRUE;
         }
         if (!CacheKey<T>::operator == (other)) {
             return FALSE;
         }
         // We know this and other are of same class because operator== on
         // CacheKey returned true.
         const LocaleCacheKey<T> *fOther =
                 static_cast<const LocaleCacheKey<T> *>(&other);
         return fLoc == fOther->fLoc;
     }
     virtual CacheKeyBase *clone() const {
         return new LocaleCacheKey<T>(*this);
     }
     virtual const T *createObject(
             const void *creationContext, UErrorCode &status) const;
     /**
      * Use the locale id as the description.
      */
     virtual char *writeDescription(char *buffer, int32_t bufLen) const {
         const char *s = fLoc.getName();
         uprv_strncpy(buffer, s, bufLen);
         buffer[bufLen - 1] = 0;
         return buffer;
     }
  
  };
  
  /**
   * The unified cache. A singleton type.
   * Design doc here:
   * https://docs.google.com/document/d/1RwGQJs4N4tawNbf809iYDRCvXoMKqDJihxzYt1ysmd8/edit?usp=sharing
   */
  class U_COMMON_API UnifiedCache : public UnifiedCacheBase {
   public:
     /**
      * @internal
      * Do not call directly. Instead use UnifiedCache::getInstance() as
      * there should be only one UnifiedCache in an application.
      */
     UnifiedCache(UErrorCode &status);
  
     /**
      * Returns the cache instance.
      */
     static UnifiedCache *getInstance(UErrorCode &status);
  
     /**
      * Fetches a value from the cache by key. Equivalent to
      * get(key, NULL, ptr, status);
      */
     template<typename T>
     void get(
             const CacheKey<T>& key,
             const T *&ptr,
             UErrorCode &status) const {
         get(key, NULL, ptr, status);
     }
  
     /**
      * Fetches value from the cache by key.
      *
      * @param key             the cache key.
      * @param creationContext passed verbatim to createObject method of key
      * @param ptr             On entry, ptr must be NULL or be included if
      *                        the reference count of the object it points
      *                        to. On exit, ptr points to the fetched object
      *                        from the cache or is left unchanged on
      *                        failure. Caller must call removeRef on ptr
      *                        if set to a non NULL value.
      * @param status          Any error returned here. May be set to a
      *                        warning value even if ptr is set.
      */
     template<typename T>
     void get(
             const CacheKey<T>& key,
             const void *creationContext,
             const T *&ptr,
             UErrorCode &status) const {
         if (U_FAILURE(status)) {
             return;
         }
         UErrorCode creationStatus = U_ZERO_ERROR;
         const SharedObject *value = NULL;
         _get(key, value, creationContext, creationStatus);
         const T *tvalue = (const T *) value;
         if (U_SUCCESS(creationStatus)) {
             SharedObject::copyPtr(tvalue, ptr);
         }
         SharedObject::clearPtr(tvalue);
         // Take care not to overwrite a warning status passed in with
         // another warning or U_ZERO_ERROR.
         if (status == U_ZERO_ERROR || U_FAILURE(creationStatus)) {
             status = creationStatus;
         }
     }
  
  #ifdef UNIFIED_CACHE_DEBUG
     /**
      * Dumps the contents of this cache to standard error. Used for testing of
      * cache only.
      */
     void dumpContents() const;
  #endif
  
     /**
      * Convenience method to get a value of type T from cache for a
      * particular locale with creationContext == NULL.
      * @param loc    the locale
      * @param ptr    On entry, must be NULL or included in the ref count
      *               of the object to which it points.
      *               On exit, fetched value stored here or is left
      *               unchanged on failure. Caller must call removeRef on
      *               ptr if set to a non NULL value.
      * @param status Any error returned here. May be set to a
      *               warning value even if ptr is set.
      */
     template<typename T>
     static void getByLocale(
             const Locale &loc, const T *&ptr, UErrorCode &status) {
         const UnifiedCache *cache = getInstance(status);
         if (U_FAILURE(status)) {
             return;
         }
         cache->get(LocaleCacheKey<T>(loc), ptr, status);
     }
  
  #ifdef UNIFIED_CACHE_DEBUG
     /**
      * Dumps the cache contents to stderr. For testing only.
      */
     static void dump();
  #endif
  
     /**
      * Returns the number of keys in this cache. For testing only.
      */
     int32_t keyCount() const;
  
     /**
      * Removes any values from cache that are not referenced outside
      * the cache.
      */
     void flush() const;
  
     /**
      * Configures at what point evcition of unused entries will begin.
      * Eviction is triggered whenever the number of unused entries exeeds
      * BOTH count AND (number of in-use items) * (percentageOfInUseItems / 100).
      * Once the number of unused entries drops below one of these,
      * eviction ceases. Because eviction happens incrementally,
      * the actual unused entry count may exceed both these numbers
      * from time to time.
      *
      * A cache entry is defined as unused if it is not essential to guarantee
      * that for a given key X, the cache returns the same reference to the
      * same value as long as the client already holds a reference to that
      * value.
      *
      * If this method is never called, the default settings are 1000 and 100%.
      *
      * Although this method is thread-safe, it is designed to be called at
      * application startup. If it is called in the middle of execution, it
      * will have no immediate effect on the cache. However over time, the
      * cache will perform eviction slices in an attempt to honor the new
      * settings.
      *
      * If a client already holds references to many different unique values
      * in the cache such that the number of those unique values far exeeds
      * "count" then the cache may not be able to maintain this maximum.
      * However, if this happens, the cache still guarantees that the number of
      * unused entries will remain only a small percentage of the total cache
      * size.
      *
      * If the parameters passed are negative, setEvctionPolicy sets status to
      * U_ILLEGAL_ARGUMENT_ERROR.
      */
     void setEvictionPolicy(
             int32_t count, int32_t percentageOfInUseItems, UErrorCode &status);
  
  
     /**
      * Returns how many entries have been auto evicted during the lifetime
      * of this cache. This only includes auto evicted entries, not
      * entries evicted because of a call to flush().
      */
     int64_t autoEvictedCount() const;
  
     /**
      * Returns the unused entry count in this cache. For testing only,
      * Regular clients will not need this.
      */
     int32_t unusedCount() const;
  
     virtual void incrementItemsInUse() const;
     virtual void decrementItemsInUseWithLockingAndEviction() const;
     virtual void decrementItemsInUse() const;
     virtual ~UnifiedCache();
   private:
     UHashtable *fHashtable;
     mutable int32_t fEvictPos;
     mutable int32_t fItemsInUseCount;
     int32_t fMaxUnused;
     int32_t fMaxPercentageOfInUse;
     mutable int64_t fAutoEvictedCount;
     UnifiedCache(const UnifiedCache &other);
     UnifiedCache &operator=(const UnifiedCache &other);
     UBool _flush(UBool all) const;
     void _get(
             const CacheKeyBase &key,
             const SharedObject *&value,
             const void *creationContext,
             UErrorCode &status) const;
     UBool _poll(
             const CacheKeyBase &key,
             const SharedObject *&value,
             UErrorCode &status) const;
     void _putNew(
             const CacheKeyBase &key,
             const SharedObject *value,
             const UErrorCode creationStatus,
             UErrorCode &status) const;
     void _putIfAbsentAndGet(
             const CacheKeyBase &key,
             const SharedObject *&value,
             UErrorCode &status) const;
     const UHashElement *_nextElement() const;
     int32_t _computeCountOfItemsToEvict() const;
     void _runEvictionSlice() const;
     void _registerMaster( 
          const CacheKeyBase *theKey, const SharedObject *value) const;
     void _put(
             const UHashElement *element,
             const SharedObject *value,
             const UErrorCode status) const;
  #ifdef UNIFIED_CACHE_DEBUG
     void _dumpContents() const;
  #endif
     static void copyPtr(const SharedObject *src, const SharedObject *&dest);
     static void clearPtr(const SharedObject *&ptr);
     static void _fetch(
             const UHashElement *element,
             const SharedObject *&value,
             UErrorCode &status);
     static UBool _inProgress(const UHashElement *element);
     static UBool _inProgress(
             const SharedObject *theValue, UErrorCode creationStatus);
     static UBool _isEvictable(const UHashElement *element);
  };
  
  U_NAMESPACE_END
  
  #endif
  