blob: 0adf95594ff082425d78fd8f7a07b7d30150e333 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "tznames.h"
#include "tznames_impl.h"
#include "unicode/locid.h"
#include "unicode/uenum.h"
#include "cmemory.h"
#include "cstring.h"
#include "putilimp.h"
#include "uassert.h"
#include "ucln_in.h"
#include "uhash.h"
#include "umutex.h"
U_NAMESPACE_BEGIN
static const UChar gEtcPrefix[] = { 0x45, 0x74, 0x63, 0x2F }; // "Etc/"
static const int32_t gEtcPrefixLen = 4;
static const UChar gSystemVPrefix[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F }; // "SystemV/
static const int32_t gSystemVPrefixLen = 8;
static const UChar gRiyadh8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38 }; // "Riyadh8"
static const int32_t gRiyadh8Len = 7;
// TimeZoneNames object cache handling
static UMTX gTimeZoneNamesLock = NULL;
static UHashtable *gTimeZoneNamesCache = NULL;
static UBool gTimeZoneNamesCacheInitialized = FALSE;
// Access count - incremented every time up to SWEEP_INTERVAL,
// then reset to 0
static int32_t gAccessCount = 0;
// Interval for calling the cache sweep function - every 100 times
#define SWEEP_INTERVAL 100
// Cache expiration in millisecond. When a cached entry is no
// longer referenced and exceeding this threshold since last
// access time, then the cache entry will be deleted by the sweep
// function. For now, 3 minutes.
#define CACHE_EXPIRATION 180000.0
typedef struct TimeZoneNamesCacheEntry {
TimeZoneNames* names;
int32_t refCount;
double lastAccess;
} TimeZoneNamesCacheEntry;
U_CDECL_BEGIN
/**
* Cleanup callback func
*/
static UBool U_CALLCONV timeZoneNames_cleanup(void)
{
umtx_destroy(&gTimeZoneNamesLock);
if (gTimeZoneNamesCache != NULL) {
uhash_close(gTimeZoneNamesCache);
gTimeZoneNamesCache = NULL;
}
gTimeZoneNamesCacheInitialized = FALSE;
return TRUE;
}
/**
* Deleter for TimeZoneNamesCacheEntry
*/
static void U_CALLCONV
deleteTimeZoneNamesCacheEntry(void *obj) {
icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
delete (icu::TimeZoneNamesImpl*) entry->names;
uprv_free(entry);
}
U_CDECL_END
/**
* Function used for removing unreferrenced cache entries exceeding
* the expiration time. This function must be called with in the mutex
* block.
*/
static void sweepCache() {
int32_t pos = -1;
const UHashElement* elem;
double now = (double)uprv_getUTCtime();
while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) {
TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer;
if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
// delete this entry
uhash_removeElement(gTimeZoneNamesCache, elem);
}
}
}
class TimeZoneNamesDelegate : public TimeZoneNames {
public:
TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
virtual ~TimeZoneNamesDelegate();
StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const;
StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const;
UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const;
UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const;
UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const;
UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const;
UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const;
TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
private:
TimeZoneNamesCacheEntry* fTZnamesCacheEntry;
};
TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
UBool initialized;
UMTX_CHECK(&gTimeZoneNamesLock, gTimeZoneNamesCacheInitialized, initialized);
if (!initialized) {
// Create empty hashtable
umtx_lock(&gTimeZoneNamesLock);
{
if (!gTimeZoneNamesCacheInitialized) {
gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
if (U_SUCCESS(status)) {
uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free);
uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry);
gTimeZoneNamesCacheInitialized = TRUE;
ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup);
}
}
}
umtx_unlock(&gTimeZoneNamesLock);
if (U_FAILURE(status)) {
return;
}
}
// Check the cache, if not available, create new one and cache
TimeZoneNamesCacheEntry *cacheEntry = NULL;
umtx_lock(&gTimeZoneNamesLock);
{
const char *key = locale.getName();
cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key);
if (cacheEntry == NULL) {
TimeZoneNames *tznames = NULL;
char *newKey = NULL;
tznames = new TimeZoneNamesImpl(locale, status);
if (tznames == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_SUCCESS(status)) {
newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
if (newKey == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
uprv_strcpy(newKey, key);
}
}
if (U_SUCCESS(status)) {
cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry));
if (cacheEntry == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
cacheEntry->names = tznames;
cacheEntry->refCount = 1;
cacheEntry->lastAccess = (double)uprv_getUTCtime();
uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
}
}
if (U_FAILURE(status)) {
if (tznames != NULL) {
delete tznames;
}
if (newKey != NULL) {
uprv_free(newKey);
}
if (cacheEntry != NULL) {
uprv_free(cacheEntry);
}
cacheEntry = NULL;
}
} else {
// Update the reference count
cacheEntry->refCount++;
cacheEntry->lastAccess = (double)uprv_getUTCtime();
}
gAccessCount++;
if (gAccessCount >= SWEEP_INTERVAL) {
// sweep
sweepCache();
gAccessCount = 0;
}
}
umtx_unlock(&gTimeZoneNamesLock);
fTZnamesCacheEntry = cacheEntry;
}
TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
umtx_lock(&gTimeZoneNamesLock);
{
U_ASSERT(fTZnamesCacheEntry->refCount > 0);
// Just decrement the reference count
fTZnamesCacheEntry->refCount--;
}
umtx_unlock(&gTimeZoneNamesLock);
}
StringEnumeration*
TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
}
StringEnumeration*
TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
}
UnicodeString&
TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
}
UnicodeString&
TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
}
UnicodeString&
TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
}
UnicodeString&
TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
}
UnicodeString&
TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
}
TimeZoneNameMatchInfo*
TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
return fTZnamesCacheEntry->names->find(text, start, types, status);
}
TimeZoneNames*
TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
return new TimeZoneNamesDelegate(locale, status);
}
UnicodeString&
TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen)
|| tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) {
name.setToBogus();
return name;
}
int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */);
if (sep > 0 && sep + 1 < tzID.length()) {
name.setTo(tzID, sep + 1);
name.findAndReplace(UnicodeString((UChar)0x5f /* _ */),
UnicodeString((UChar)0x20 /* space */));
} else {
name.setToBogus();
}
return name;
}
UnicodeString&
TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
getTimeZoneDisplayName(tzID, type, name);
if (name.isEmpty()) {
UnicodeString mzID;
getMetaZoneID(tzID, date, mzID);
getMetaZoneDisplayName(mzID, type, name);
}
return name;
}
U_NAMESPACE_END
#endif