1/*
2*******************************************************************************
3* Copyright (C) 2011-2013, 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 "mutex.h"
18#include "putilimp.h"
19#include "tznames_impl.h"
20#include "uassert.h"
21#include "ucln_in.h"
22#include "uhash.h"
23#include "umutex.h"
24#include "uvector.h"
25
26
27U_NAMESPACE_BEGIN
28
29// TimeZoneNames object cache handling
30static UMutex gTimeZoneNamesLock = U_MUTEX_INITIALIZER;
31static UHashtable *gTimeZoneNamesCache = NULL;
32static UBool gTimeZoneNamesCacheInitialized = FALSE;
33
34// Access count - incremented every time up to SWEEP_INTERVAL,
35// then reset to 0
36static int32_t gAccessCount = 0;
37
38// Interval for calling the cache sweep function - every 100 times
39#define SWEEP_INTERVAL 100
40
41// Cache expiration in millisecond. When a cached entry is no
42// longer referenced and exceeding this threshold since last
43// access time, then the cache entry will be deleted by the sweep
44// function. For now, 3 minutes.
45#define CACHE_EXPIRATION 180000.0
46
47typedef struct TimeZoneNamesCacheEntry {
48    TimeZoneNames*  names;
49    int32_t         refCount;
50    double          lastAccess;
51} TimeZoneNamesCacheEntry;
52
53U_CDECL_BEGIN
54/**
55 * Cleanup callback func
56 */
57static UBool U_CALLCONV timeZoneNames_cleanup(void)
58{
59    if (gTimeZoneNamesCache != NULL) {
60        uhash_close(gTimeZoneNamesCache);
61        gTimeZoneNamesCache = NULL;
62    }
63    gTimeZoneNamesCacheInitialized = FALSE;
64    return TRUE;
65}
66
67/**
68 * Deleter for TimeZoneNamesCacheEntry
69 */
70static void U_CALLCONV
71deleteTimeZoneNamesCacheEntry(void *obj) {
72    icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
73    delete (icu::TimeZoneNamesImpl*) entry->names;
74    uprv_free(entry);
75}
76U_CDECL_END
77
78/**
79 * Function used for removing unreferrenced cache entries exceeding
80 * the expiration time. This function must be called with in the mutex
81 * block.
82 */
83static void sweepCache() {
84    int32_t pos = -1;
85    const UHashElement* elem;
86    double now = (double)uprv_getUTCtime();
87
88    while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) {
89        TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer;
90        if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
91            // delete this entry
92            uhash_removeElement(gTimeZoneNamesCache, elem);
93        }
94    }
95}
96
97// ---------------------------------------------------
98// TimeZoneNamesDelegate
99// ---------------------------------------------------
100class TimeZoneNamesDelegate : public TimeZoneNames {
101public:
102    TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
103    virtual ~TimeZoneNamesDelegate();
104
105    virtual UBool operator==(const TimeZoneNames& other) const;
106    virtual UBool operator!=(const TimeZoneNames& other) const {return !operator==(other);};
107    virtual TimeZoneNames* clone() const;
108
109    StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const;
110    StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const;
111    UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const;
112    UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const;
113
114    UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const;
115    UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const;
116
117    UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const;
118
119    MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
120private:
121    TimeZoneNamesDelegate();
122    TimeZoneNamesCacheEntry*    fTZnamesCacheEntry;
123};
124
125TimeZoneNamesDelegate::TimeZoneNamesDelegate()
126: fTZnamesCacheEntry(0) {
127}
128
129TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
130    Mutex lock(&gTimeZoneNamesLock);
131    if (!gTimeZoneNamesCacheInitialized) {
132        // Create empty hashtable if it is not already initialized.
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    if (U_FAILURE(status)) {
143        return;
144    }
145
146    // Check the cache, if not available, create new one and cache
147    TimeZoneNamesCacheEntry *cacheEntry = NULL;
148
149    const char *key = locale.getName();
150    cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key);
151    if (cacheEntry == NULL) {
152        TimeZoneNames *tznames = NULL;
153        char *newKey = NULL;
154
155        tznames = new TimeZoneNamesImpl(locale, status);
156        if (tznames == NULL) {
157            status = U_MEMORY_ALLOCATION_ERROR;
158        }
159        if (U_SUCCESS(status)) {
160            newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
161            if (newKey == NULL) {
162                status = U_MEMORY_ALLOCATION_ERROR;
163            } else {
164                uprv_strcpy(newKey, key);
165            }
166        }
167        if (U_SUCCESS(status)) {
168            cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry));
169            if (cacheEntry == NULL) {
170                status = U_MEMORY_ALLOCATION_ERROR;
171            } else {
172                cacheEntry->names = tznames;
173                cacheEntry->refCount = 1;
174                cacheEntry->lastAccess = (double)uprv_getUTCtime();
175
176                uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
177            }
178        }
179        if (U_FAILURE(status)) {
180            if (tznames != NULL) {
181                delete tznames;
182            }
183            if (newKey != NULL) {
184                uprv_free(newKey);
185            }
186            if (cacheEntry != NULL) {
187                uprv_free(cacheEntry);
188            }
189            cacheEntry = NULL;
190        }
191    } else {
192        // Update the reference count
193        cacheEntry->refCount++;
194        cacheEntry->lastAccess = (double)uprv_getUTCtime();
195    }
196    gAccessCount++;
197    if (gAccessCount >= SWEEP_INTERVAL) {
198        // sweep
199        sweepCache();
200        gAccessCount = 0;
201    }
202    fTZnamesCacheEntry = cacheEntry;
203}
204
205TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
206    umtx_lock(&gTimeZoneNamesLock);
207    {
208        if (fTZnamesCacheEntry) {
209            U_ASSERT(fTZnamesCacheEntry->refCount > 0);
210            // Just decrement the reference count
211            fTZnamesCacheEntry->refCount--;
212        }
213    }
214    umtx_unlock(&gTimeZoneNamesLock);
215}
216
217UBool
218TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const {
219    if (this == &other) {
220        return TRUE;
221    }
222    // Just compare if the other object also use the same
223    // cache entry
224    const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other);
225    if (rhs) {
226        return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry;
227    }
228    return FALSE;
229}
230
231TimeZoneNames*
232TimeZoneNamesDelegate::clone() const {
233    TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate();
234    if (other != NULL) {
235        umtx_lock(&gTimeZoneNamesLock);
236        {
237            // Just increment the reference count
238            fTZnamesCacheEntry->refCount++;
239            other->fTZnamesCacheEntry = fTZnamesCacheEntry;
240        }
241        umtx_unlock(&gTimeZoneNamesLock);
242    }
243    return other;
244}
245
246StringEnumeration*
247TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
248    return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
249}
250
251StringEnumeration*
252TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
253    return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
254}
255
256UnicodeString&
257TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
258    return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
259}
260
261UnicodeString&
262TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
263    return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
264}
265
266UnicodeString&
267TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
268    return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
269}
270
271UnicodeString&
272TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
273    return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
274}
275
276UnicodeString&
277TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
278    return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
279}
280
281TimeZoneNames::MatchInfoCollection*
282TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
283    return fTZnamesCacheEntry->names->find(text, start, types, status);
284}
285
286// ---------------------------------------------------
287// TimeZoneNames base class
288// ---------------------------------------------------
289TimeZoneNames::~TimeZoneNames() {
290}
291
292TimeZoneNames*
293TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
294    return new TimeZoneNamesDelegate(locale, status);
295}
296
297UnicodeString&
298TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
299    return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name);
300}
301
302UnicodeString&
303TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
304    getTimeZoneDisplayName(tzID, type, name);
305    if (name.isEmpty()) {
306        UnicodeString mzID;
307        getMetaZoneID(tzID, date, mzID);
308        getMetaZoneDisplayName(mzID, type, name);
309    }
310    return name;
311}
312
313
314struct MatchInfo : UMemory {
315    UTimeZoneNameType nameType;
316    UnicodeString id;
317    int32_t matchLength;
318    UBool isTZID;
319
320    MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) {
321        this->nameType = nameType;
322        this->matchLength = matchLength;
323        if (tzID != NULL) {
324            this->id.setTo(*tzID);
325            this->isTZID = TRUE;
326        } else {
327            this->id.setTo(*mzID);
328            this->isTZID = FALSE;
329        }
330    }
331};
332
333U_CDECL_BEGIN
334static void U_CALLCONV
335deleteMatchInfo(void *obj) {
336    delete static_cast<MatchInfo *>(obj);
337}
338U_CDECL_END
339
340// ---------------------------------------------------
341// MatchInfoCollection class
342// ---------------------------------------------------
343TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
344: fMatches(NULL) {
345}
346
347TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
348    if (fMatches != NULL) {
349        delete fMatches;
350    }
351}
352
353void
354TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength,
355            const UnicodeString& tzID, UErrorCode& status) {
356    if (U_FAILURE(status)) {
357        return;
358    }
359    MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, &tzID, NULL);
360    if (matchInfo == NULL) {
361        status = U_MEMORY_ALLOCATION_ERROR;
362        return;
363    }
364    matches(status)->addElement(matchInfo, status);
365    if (U_FAILURE(status)) {
366        delete matchInfo;
367    }
368}
369
370void
371TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength,
372            const UnicodeString& mzID, UErrorCode& status) {
373    if (U_FAILURE(status)) {
374        return;
375    }
376    MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, NULL, &mzID);
377    if (matchInfo == NULL) {
378        status = U_MEMORY_ALLOCATION_ERROR;
379        return;
380    }
381    matches(status)->addElement(matchInfo, status);
382    if (U_FAILURE(status)) {
383        delete matchInfo;
384    }
385}
386
387int32_t
388TimeZoneNames::MatchInfoCollection::size() const {
389    if (fMatches == NULL) {
390        return 0;
391    }
392    return fMatches->size();
393}
394
395UTimeZoneNameType
396TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const {
397    const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
398    if (match) {
399        return match->nameType;
400    }
401    return UTZNM_UNKNOWN;
402}
403
404int32_t
405TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const {
406    const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
407    if (match) {
408        return match->matchLength;
409    }
410    return 0;
411}
412
413UBool
414TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const {
415    tzID.remove();
416    const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
417    if (match && match->isTZID) {
418        tzID.setTo(match->id);
419        return TRUE;
420    }
421    return FALSE;
422}
423
424UBool
425TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const {
426    mzID.remove();
427    const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
428    if (match && !match->isTZID) {
429        mzID.setTo(match->id);
430        return TRUE;
431    }
432    return FALSE;
433}
434
435UVector*
436TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) {
437    if (U_FAILURE(status)) {
438        return NULL;
439    }
440    if (fMatches != NULL) {
441        return fMatches;
442    }
443    fMatches = new UVector(deleteMatchInfo, NULL, status);
444    if (fMatches == NULL) {
445        status = U_MEMORY_ALLOCATION_ERROR;
446    } else if (U_FAILURE(status)) {
447        delete fMatches;
448        fMatches = NULL;
449    }
450    return fMatches;
451}
452
453
454U_NAMESPACE_END
455#endif
456