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