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
26U_NAMESPACE_BEGIN
27
28static const UChar gEtcPrefix[]         = { 0x45, 0x74, 0x63, 0x2F }; // "Etc/"
29static const int32_t gEtcPrefixLen      = 4;
30static const UChar gSystemVPrefix[]     = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F }; // "SystemV/
31static const int32_t gSystemVPrefixLen  = 8;
32static const UChar gRiyadh8[]           = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38 }; // "Riyadh8"
33static const int32_t gRiyadh8Len       = 7;
34
35// TimeZoneNames object cache handling
36static UMutex gTimeZoneNamesLock = U_MUTEX_INITIALIZER;
37static UHashtable *gTimeZoneNamesCache = NULL;
38static UBool gTimeZoneNamesCacheInitialized = FALSE;
39
40// Access count - incremented every time up to SWEEP_INTERVAL,
41// then reset to 0
42static 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
53typedef struct TimeZoneNamesCacheEntry {
54    TimeZoneNames*  names;
55    int32_t         refCount;
56    double          lastAccess;
57} TimeZoneNamesCacheEntry;
58
59U_CDECL_BEGIN
60/**
61 * Cleanup callback func
62 */
63static 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 */
76static void U_CALLCONV
77deleteTimeZoneNamesCacheEntry(void *obj) {
78    icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
79    delete (icu::TimeZoneNamesImpl*) entry->names;
80    uprv_free(entry);
81}
82U_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 */
89static 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// ---------------------------------------------------
106class TimeZoneNamesDelegate : public TimeZoneNames {
107public:
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;
126private:
127    TimeZoneNamesDelegate();
128    TimeZoneNamesCacheEntry*    fTZnamesCacheEntry;
129};
130
131TimeZoneNamesDelegate::TimeZoneNamesDelegate()
132: fTZnamesCacheEntry(0) {
133}
134
135TimeZoneNamesDelegate::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
222TimeZoneNamesDelegate::~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
234UBool
235TimeZoneNamesDelegate::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
248TimeZoneNames*
249TimeZoneNamesDelegate::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
263StringEnumeration*
264TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
265    return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
266}
267
268StringEnumeration*
269TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
270    return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
271}
272
273UnicodeString&
274TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
275    return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
276}
277
278UnicodeString&
279TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
280    return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
281}
282
283UnicodeString&
284TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
285    return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
286}
287
288UnicodeString&
289TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
290    return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
291}
292
293UnicodeString&
294TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
295    return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
296}
297
298TimeZoneNames::MatchInfoCollection*
299TimeZoneNamesDelegate::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// ---------------------------------------------------
306UOBJECT_DEFINE_NO_RTTI_IMPLEMENTATION(TimeZoneNames)
307
308TimeZoneNames::~TimeZoneNames() {
309}
310
311TimeZoneNames*
312TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
313    return new TimeZoneNamesDelegate(locale, status);
314}
315
316UnicodeString&
317TimeZoneNames::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
335UnicodeString&
336TimeZoneNames::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
347struct 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
366U_CDECL_BEGIN
367static void U_CALLCONV
368deleteMatchInfo(void *obj) {
369    delete static_cast<MatchInfo *>(obj);
370}
371U_CDECL_END
372
373// ---------------------------------------------------
374// MatchInfoCollection class
375// ---------------------------------------------------
376TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
377: fMatches(NULL) {
378}
379
380TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
381    if (fMatches != NULL) {
382        delete fMatches;
383    }
384}
385
386void
387TimeZoneNames::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
403void
404TimeZoneNames::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
420int32_t
421TimeZoneNames::MatchInfoCollection::size() const {
422    if (fMatches == NULL) {
423        return 0;
424    }
425    return fMatches->size();
426}
427
428UTimeZoneNameType
429TimeZoneNames::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
437int32_t
438TimeZoneNames::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
446UBool
447TimeZoneNames::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
457UBool
458TimeZoneNames::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
468UVector*
469TimeZoneNames::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
487U_NAMESPACE_END
488#endif
489