Home | History | Annotate | Download | only in i18n
      1 /*
      2 *******************************************************************************
      3 * Copyright (C) 2011, International Business Machines Corporation and         *
      4 * others. All Rights Reserved.                                                *
      5 *******************************************************************************
      6 */
      7 
      8 #include "unicode/utypes.h"
      9 
     10 #if !UCONFIG_NO_FORMATTING
     11 
     12 #include "tznames.h"
     13 #include "tznames_impl.h"
     14 
     15 #include "unicode/locid.h"
     16 #include "unicode/uenum.h"
     17 #include "cmemory.h"
     18 #include "cstring.h"
     19 #include "putilimp.h"
     20 #include "uassert.h"
     21 #include "ucln_in.h"
     22 #include "uhash.h"
     23 #include "umutex.h"
     24 
     25 
     26 U_NAMESPACE_BEGIN
     27 
     28 static const UChar gEtcPrefix[]         = { 0x45, 0x74, 0x63, 0x2F }; // "Etc/"
     29 static const int32_t gEtcPrefixLen      = 4;
     30 static const UChar gSystemVPrefix[]     = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F }; // "SystemV/
     31 static const int32_t gSystemVPrefixLen  = 8;
     32 static const UChar gRiyadh8[]           = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38 }; // "Riyadh8"
     33 static const int32_t gRiyadh8Len       = 7;
     34 
     35 // TimeZoneNames object cache handling
     36 static UMTX gTimeZoneNamesLock = NULL;
     37 static UHashtable *gTimeZoneNamesCache = NULL;
     38 static UBool gTimeZoneNamesCacheInitialized = FALSE;
     39 
     40 // Access count - incremented every time up to SWEEP_INTERVAL,
     41 // then reset to 0
     42 static int32_t gAccessCount = 0;
     43 
     44 // Interval for calling the cache sweep function - every 100 times
     45 #define SWEEP_INTERVAL 100
     46 
     47 // Cache expiration in millisecond. When a cached entry is no
     48 // longer referenced and exceeding this threshold since last
     49 // access time, then the cache entry will be deleted by the sweep
     50 // function. For now, 3 minutes.
     51 #define CACHE_EXPIRATION 180000.0
     52 
     53 typedef struct TimeZoneNamesCacheEntry {
     54     TimeZoneNames*  names;
     55     int32_t         refCount;
     56     double          lastAccess;
     57 } TimeZoneNamesCacheEntry;
     58 
     59 U_CDECL_BEGIN
     60 /**
     61  * Cleanup callback func
     62  */
     63 static UBool U_CALLCONV timeZoneNames_cleanup(void)
     64 {
     65     umtx_destroy(&gTimeZoneNamesLock);
     66 
     67     if (gTimeZoneNamesCache != NULL) {
     68         uhash_close(gTimeZoneNamesCache);
     69         gTimeZoneNamesCache = NULL;
     70     }
     71     gTimeZoneNamesCacheInitialized = FALSE;
     72     return TRUE;
     73 }
     74 
     75 /**
     76  * Deleter for TimeZoneNamesCacheEntry
     77  */
     78 static void U_CALLCONV
     79 deleteTimeZoneNamesCacheEntry(void *obj) {
     80     icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
     81     delete (icu::TimeZoneNamesImpl*) entry->names;
     82     uprv_free(entry);
     83 }
     84 U_CDECL_END
     85 
     86 /**
     87  * Function used for removing unreferrenced cache entries exceeding
     88  * the expiration time. This function must be called with in the mutex
     89  * block.
     90  */
     91 static void sweepCache() {
     92     int32_t pos = -1;
     93     const UHashElement* elem;
     94     double now = (double)uprv_getUTCtime();
     95 
     96     while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) {
     97         TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer;
     98         if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
     99             // delete this entry
    100             uhash_removeElement(gTimeZoneNamesCache, elem);
    101         }
    102     }
    103 }
    104 
    105 class TimeZoneNamesDelegate : public TimeZoneNames {
    106 public:
    107     TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
    108     virtual ~TimeZoneNamesDelegate();
    109 
    110     StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const;
    111     StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const;
    112     UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const;
    113     UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const;
    114 
    115     UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const;
    116     UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const;
    117 
    118     UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const;
    119 
    120     TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
    121 private:
    122     TimeZoneNamesCacheEntry*    fTZnamesCacheEntry;
    123 };
    124 
    125 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
    126     UBool initialized;
    127     UMTX_CHECK(&gTimeZoneNamesLock, gTimeZoneNamesCacheInitialized, initialized);
    128     if (!initialized) {
    129         // Create empty hashtable
    130         umtx_lock(&gTimeZoneNamesLock);
    131         {
    132             if (!gTimeZoneNamesCacheInitialized) {
    133                 gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
    134                 if (U_SUCCESS(status)) {
    135                     uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free);
    136                     uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry);
    137                     gTimeZoneNamesCacheInitialized = TRUE;
    138                     ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup);
    139                 }
    140             }
    141         }
    142         umtx_unlock(&gTimeZoneNamesLock);
    143 
    144         if (U_FAILURE(status)) {
    145             return;
    146         }
    147     }
    148 
    149     // Check the cache, if not available, create new one and cache
    150     TimeZoneNamesCacheEntry *cacheEntry = NULL;
    151     umtx_lock(&gTimeZoneNamesLock);
    152     {
    153         const char *key = locale.getName();
    154         cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key);
    155         if (cacheEntry == NULL) {
    156             TimeZoneNames *tznames = NULL;
    157             char *newKey = NULL;
    158 
    159             tznames = new TimeZoneNamesImpl(locale, status);
    160             if (tznames == NULL) {
    161                 status = U_MEMORY_ALLOCATION_ERROR;
    162             }
    163             if (U_SUCCESS(status)) {
    164                 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
    165                 if (newKey == NULL) {
    166                     status = U_MEMORY_ALLOCATION_ERROR;
    167                 } else {
    168                     uprv_strcpy(newKey, key);
    169                 }
    170             }
    171             if (U_SUCCESS(status)) {
    172                 cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry));
    173                 if (cacheEntry == NULL) {
    174                     status = U_MEMORY_ALLOCATION_ERROR;
    175                 } else {
    176                     cacheEntry->names = tznames;
    177                     cacheEntry->refCount = 1;
    178                     cacheEntry->lastAccess = (double)uprv_getUTCtime();
    179 
    180                     uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
    181                 }
    182             }
    183             if (U_FAILURE(status)) {
    184                 if (tznames != NULL) {
    185                     delete tznames;
    186                 }
    187                 if (newKey != NULL) {
    188                     uprv_free(newKey);
    189                 }
    190                 if (cacheEntry != NULL) {
    191                     uprv_free(cacheEntry);
    192                 }
    193                 cacheEntry = NULL;
    194             }
    195         } else {
    196             // Update the reference count
    197             cacheEntry->refCount++;
    198             cacheEntry->lastAccess = (double)uprv_getUTCtime();
    199         }
    200         gAccessCount++;
    201         if (gAccessCount >= SWEEP_INTERVAL) {
    202             // sweep
    203             sweepCache();
    204             gAccessCount = 0;
    205         }
    206     }
    207     umtx_unlock(&gTimeZoneNamesLock);
    208 
    209     fTZnamesCacheEntry = cacheEntry;
    210 }
    211 
    212 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
    213     umtx_lock(&gTimeZoneNamesLock);
    214     {
    215         U_ASSERT(fTZnamesCacheEntry->refCount > 0);
    216         // Just decrement the reference count
    217         fTZnamesCacheEntry->refCount--;
    218     }
    219     umtx_unlock(&gTimeZoneNamesLock);
    220 }
    221 
    222 StringEnumeration*
    223 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
    224     return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
    225 }
    226 
    227 StringEnumeration*
    228 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
    229     return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
    230 }
    231 
    232 UnicodeString&
    233 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
    234     return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
    235 }
    236 
    237 UnicodeString&
    238 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
    239     return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
    240 }
    241 
    242 UnicodeString&
    243 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
    244     return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
    245 }
    246 
    247 UnicodeString&
    248 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
    249     return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
    250 }
    251 
    252 UnicodeString&
    253 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
    254     return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
    255 }
    256 
    257 TimeZoneNameMatchInfo*
    258 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
    259     return fTZnamesCacheEntry->names->find(text, start, types, status);
    260 }
    261 
    262 
    263 
    264 TimeZoneNames*
    265 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
    266     return new TimeZoneNamesDelegate(locale, status);
    267 }
    268 
    269 UnicodeString&
    270 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
    271     if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen)
    272         || tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) {
    273         name.setToBogus();
    274         return name;
    275     }
    276 
    277     int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */);
    278     if (sep > 0 && sep + 1 < tzID.length()) {
    279         name.setTo(tzID, sep + 1);
    280         name.findAndReplace(UnicodeString((UChar)0x5f /* _ */),
    281                             UnicodeString((UChar)0x20 /* space */));
    282     } else {
    283         name.setToBogus();
    284     }
    285     return name;
    286 }
    287 
    288 UnicodeString&
    289 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
    290     getTimeZoneDisplayName(tzID, type, name);
    291     if (name.isEmpty()) {
    292         UnicodeString mzID;
    293         getMetaZoneID(tzID, date, mzID);
    294         getMetaZoneDisplayName(mzID, type, name);
    295     }
    296     return name;
    297 }
    298 
    299 U_NAMESPACE_END
    300 #endif
    301