Home | History | Annotate | Download | only in i18n
      1 /*
      2 *******************************************************************************
      3 * Copyright (C) 2011-2012, 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 "unicode/locid.h"
     13 #include "unicode/tznames.h"
     14 #include "unicode/uenum.h"
     15 #include "cmemory.h"
     16 #include "cstring.h"
     17 #include "putilimp.h"
     18 #include "tznames_impl.h"
     19 #include "uassert.h"
     20 #include "ucln_in.h"
     21 #include "uhash.h"
     22 #include "umutex.h"
     23 #include "uvector.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 UMutex gTimeZoneNamesLock = U_MUTEX_INITIALIZER;
     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     if (gTimeZoneNamesCache != NULL) {
     66         uhash_close(gTimeZoneNamesCache);
     67         gTimeZoneNamesCache = NULL;
     68     }
     69     gTimeZoneNamesCacheInitialized = FALSE;
     70     return TRUE;
     71 }
     72 
     73 /**
     74  * Deleter for TimeZoneNamesCacheEntry
     75  */
     76 static void U_CALLCONV
     77 deleteTimeZoneNamesCacheEntry(void *obj) {
     78     icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
     79     delete (icu::TimeZoneNamesImpl*) entry->names;
     80     uprv_free(entry);
     81 }
     82 U_CDECL_END
     83 
     84 /**
     85  * Function used for removing unreferrenced cache entries exceeding
     86  * the expiration time. This function must be called with in the mutex
     87  * block.
     88  */
     89 static void sweepCache() {
     90     int32_t pos = -1;
     91     const UHashElement* elem;
     92     double now = (double)uprv_getUTCtime();
     93 
     94     while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) {
     95         TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer;
     96         if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
     97             // delete this entry
     98             uhash_removeElement(gTimeZoneNamesCache, elem);
     99         }
    100     }
    101 }
    102 
    103 // ---------------------------------------------------
    104 // TimeZoneNamesDelegate
    105 // ---------------------------------------------------
    106 class TimeZoneNamesDelegate : public TimeZoneNames {
    107 public:
    108     TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
    109     virtual ~TimeZoneNamesDelegate();
    110 
    111     virtual UBool operator==(const TimeZoneNames& other) const;
    112     virtual UBool operator!=(const TimeZoneNames& other) const {return !operator==(other);};
    113     virtual TimeZoneNames* clone() const;
    114 
    115     StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const;
    116     StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const;
    117     UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const;
    118     UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const;
    119 
    120     UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const;
    121     UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const;
    122 
    123     UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const;
    124 
    125     MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
    126 private:
    127     TimeZoneNamesDelegate();
    128     TimeZoneNamesCacheEntry*    fTZnamesCacheEntry;
    129 };
    130 
    131 TimeZoneNamesDelegate::TimeZoneNamesDelegate()
    132 : fTZnamesCacheEntry(0) {
    133 }
    134 
    135 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
    136     UBool initialized;
    137     UMTX_CHECK(&gTimeZoneNamesLock, gTimeZoneNamesCacheInitialized, initialized);
    138     if (!initialized) {
    139         // Create empty hashtable
    140         umtx_lock(&gTimeZoneNamesLock);
    141         {
    142             if (!gTimeZoneNamesCacheInitialized) {
    143                 gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
    144                 if (U_SUCCESS(status)) {
    145                     uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free);
    146                     uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry);
    147                     gTimeZoneNamesCacheInitialized = TRUE;
    148                     ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup);
    149                 }
    150             }
    151         }
    152         umtx_unlock(&gTimeZoneNamesLock);
    153 
    154         if (U_FAILURE(status)) {
    155             return;
    156         }
    157     }
    158 
    159     // Check the cache, if not available, create new one and cache
    160     TimeZoneNamesCacheEntry *cacheEntry = NULL;
    161     umtx_lock(&gTimeZoneNamesLock);
    162     {
    163         const char *key = locale.getName();
    164         cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key);
    165         if (cacheEntry == NULL) {
    166             TimeZoneNames *tznames = NULL;
    167             char *newKey = NULL;
    168 
    169             tznames = new TimeZoneNamesImpl(locale, status);
    170             if (tznames == NULL) {
    171                 status = U_MEMORY_ALLOCATION_ERROR;
    172             }
    173             if (U_SUCCESS(status)) {
    174                 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
    175                 if (newKey == NULL) {
    176                     status = U_MEMORY_ALLOCATION_ERROR;
    177                 } else {
    178                     uprv_strcpy(newKey, key);
    179                 }
    180             }
    181             if (U_SUCCESS(status)) {
    182                 cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry));
    183                 if (cacheEntry == NULL) {
    184                     status = U_MEMORY_ALLOCATION_ERROR;
    185                 } else {
    186                     cacheEntry->names = tznames;
    187                     cacheEntry->refCount = 1;
    188                     cacheEntry->lastAccess = (double)uprv_getUTCtime();
    189 
    190                     uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
    191                 }
    192             }
    193             if (U_FAILURE(status)) {
    194                 if (tznames != NULL) {
    195                     delete tznames;
    196                 }
    197                 if (newKey != NULL) {
    198                     uprv_free(newKey);
    199                 }
    200                 if (cacheEntry != NULL) {
    201                     uprv_free(cacheEntry);
    202                 }
    203                 cacheEntry = NULL;
    204             }
    205         } else {
    206             // Update the reference count
    207             cacheEntry->refCount++;
    208             cacheEntry->lastAccess = (double)uprv_getUTCtime();
    209         }
    210         gAccessCount++;
    211         if (gAccessCount >= SWEEP_INTERVAL) {
    212             // sweep
    213             sweepCache();
    214             gAccessCount = 0;
    215         }
    216     }
    217     umtx_unlock(&gTimeZoneNamesLock);
    218 
    219     fTZnamesCacheEntry = cacheEntry;
    220 }
    221 
    222 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
    223     umtx_lock(&gTimeZoneNamesLock);
    224     {
    225         if (fTZnamesCacheEntry) {
    226             U_ASSERT(fTZnamesCacheEntry->refCount > 0);
    227             // Just decrement the reference count
    228             fTZnamesCacheEntry->refCount--;
    229         }
    230     }
    231     umtx_unlock(&gTimeZoneNamesLock);
    232 }
    233 
    234 UBool
    235 TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const {
    236     if (this == &other) {
    237         return TRUE;
    238     }
    239     // Just compare if the other object also use the same
    240     // cache entry
    241     const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other);
    242     if (rhs) {
    243         return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry;
    244     }
    245     return FALSE;
    246 }
    247 
    248 TimeZoneNames*
    249 TimeZoneNamesDelegate::clone() const {
    250     TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate();
    251     if (other != NULL) {
    252         umtx_lock(&gTimeZoneNamesLock);
    253         {
    254             // Just increment the reference count
    255             fTZnamesCacheEntry->refCount++;
    256             other->fTZnamesCacheEntry = fTZnamesCacheEntry;
    257         }
    258         umtx_unlock(&gTimeZoneNamesLock);
    259     }
    260     return other;
    261 }
    262 
    263 StringEnumeration*
    264 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
    265     return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
    266 }
    267 
    268 StringEnumeration*
    269 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
    270     return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
    271 }
    272 
    273 UnicodeString&
    274 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
    275     return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
    276 }
    277 
    278 UnicodeString&
    279 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
    280     return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
    281 }
    282 
    283 UnicodeString&
    284 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
    285     return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
    286 }
    287 
    288 UnicodeString&
    289 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
    290     return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
    291 }
    292 
    293 UnicodeString&
    294 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
    295     return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
    296 }
    297 
    298 TimeZoneNames::MatchInfoCollection*
    299 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
    300     return fTZnamesCacheEntry->names->find(text, start, types, status);
    301 }
    302 
    303 // ---------------------------------------------------
    304 // TimeZoneNames base class
    305 // ---------------------------------------------------
    306 UOBJECT_DEFINE_NO_RTTI_IMPLEMENTATION(TimeZoneNames)
    307 
    308 TimeZoneNames::~TimeZoneNames() {
    309 }
    310 
    311 TimeZoneNames*
    312 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
    313     return new TimeZoneNamesDelegate(locale, status);
    314 }
    315 
    316 UnicodeString&
    317 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
    318     if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen)
    319         || tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) {
    320         name.setToBogus();
    321         return name;
    322     }
    323 
    324     int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */);
    325     if (sep > 0 && sep + 1 < tzID.length()) {
    326         name.setTo(tzID, sep + 1);
    327         name.findAndReplace(UnicodeString((UChar)0x5f /* _ */),
    328                             UnicodeString((UChar)0x20 /* space */));
    329     } else {
    330         name.setToBogus();
    331     }
    332     return name;
    333 }
    334 
    335 UnicodeString&
    336 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
    337     getTimeZoneDisplayName(tzID, type, name);
    338     if (name.isEmpty()) {
    339         UnicodeString mzID;
    340         getMetaZoneID(tzID, date, mzID);
    341         getMetaZoneDisplayName(mzID, type, name);
    342     }
    343     return name;
    344 }
    345 
    346 
    347 struct MatchInfo : UMemory {
    348     UTimeZoneNameType nameType;
    349     UnicodeString id;
    350     int32_t matchLength;
    351     UBool isTZID;
    352 
    353     MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) {
    354         this->nameType = nameType;
    355         this->matchLength = matchLength;
    356         if (tzID != NULL) {
    357             this->id.setTo(*tzID);
    358             this->isTZID = TRUE;
    359         } else {
    360             this->id.setTo(*mzID);
    361             this->isTZID = FALSE;
    362         }
    363     }
    364 };
    365 
    366 U_CDECL_BEGIN
    367 static void U_CALLCONV
    368 deleteMatchInfo(void *obj) {
    369     delete static_cast<MatchInfo *>(obj);
    370 }
    371 U_CDECL_END
    372 
    373 // ---------------------------------------------------
    374 // MatchInfoCollection class
    375 // ---------------------------------------------------
    376 TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
    377 : fMatches(NULL) {
    378 }
    379 
    380 TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
    381     if (fMatches != NULL) {
    382         delete fMatches;
    383     }
    384 }
    385 
    386 void
    387 TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength,
    388             const UnicodeString& tzID, UErrorCode& status) {
    389     if (U_FAILURE(status)) {
    390         return;
    391     }
    392     MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, &tzID, NULL);
    393     if (matchInfo == NULL) {
    394         status = U_MEMORY_ALLOCATION_ERROR;
    395         return;
    396     }
    397     matches(status)->addElement(matchInfo, status);
    398     if (U_FAILURE(status)) {
    399         delete matchInfo;
    400     }
    401 }
    402 
    403 void
    404 TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength,
    405             const UnicodeString& mzID, UErrorCode& status) {
    406     if (U_FAILURE(status)) {
    407         return;
    408     }
    409     MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, NULL, &mzID);
    410     if (matchInfo == NULL) {
    411         status = U_MEMORY_ALLOCATION_ERROR;
    412         return;
    413     }
    414     matches(status)->addElement(matchInfo, status);
    415     if (U_FAILURE(status)) {
    416         delete matchInfo;
    417     }
    418 }
    419 
    420 int32_t
    421 TimeZoneNames::MatchInfoCollection::size() const {
    422     if (fMatches == NULL) {
    423         return 0;
    424     }
    425     return fMatches->size();
    426 }
    427 
    428 UTimeZoneNameType
    429 TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const {
    430     const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
    431     if (match) {
    432         return match->nameType;
    433     }
    434     return UTZNM_UNKNOWN;
    435 }
    436 
    437 int32_t
    438 TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const {
    439     const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
    440     if (match) {
    441         return match->matchLength;
    442     }
    443     return 0;
    444 }
    445 
    446 UBool
    447 TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const {
    448     tzID.remove();
    449     const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
    450     if (match && match->isTZID) {
    451         tzID.setTo(match->id);
    452         return TRUE;
    453     }
    454     return FALSE;
    455 }
    456 
    457 UBool
    458 TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const {
    459     mzID.remove();
    460     const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
    461     if (match && !match->isTZID) {
    462         mzID.setTo(match->id);
    463         return TRUE;
    464     }
    465     return FALSE;
    466 }
    467 
    468 UVector*
    469 TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) {
    470     if (U_FAILURE(status)) {
    471         return NULL;
    472     }
    473     if (fMatches != NULL) {
    474         return fMatches;
    475     }
    476     fMatches = new UVector(deleteMatchInfo, NULL, status);
    477     if (fMatches == NULL) {
    478         status = U_MEMORY_ALLOCATION_ERROR;
    479     } else if (U_FAILURE(status)) {
    480         delete fMatches;
    481         fMatches = NULL;
    482     }
    483     return fMatches;
    484 }
    485 
    486 
    487 U_NAMESPACE_END
    488 #endif
    489