1/*
2 *******************************************************************************
3 * Copyright (C) 2002-2011, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
6 */
7#include "unicode/utypes.h"
8
9#if !UCONFIG_NO_SERVICE || !UCONFIG_NO_TRANSLITERATION
10
11#include "unicode/resbund.h"
12#include "cmemory.h"
13#include "ustrfmt.h"
14#include "locutil.h"
15#include "charstr.h"
16#include "ucln_cmn.h"
17#include "uassert.h"
18#include "umutex.h"
19
20// see LocaleUtility::getAvailableLocaleNames
21static icu::Hashtable * LocaleUtility_cache = NULL;
22
23#define UNDERSCORE_CHAR ((UChar)0x005f)
24#define AT_SIGN_CHAR    ((UChar)64)
25#define PERIOD_CHAR     ((UChar)46)
26
27/*
28 ******************************************************************
29 */
30
31/**
32 * Release all static memory held by Locale Utility.
33 */
34U_CDECL_BEGIN
35static UBool U_CALLCONV service_cleanup(void) {
36    if (LocaleUtility_cache) {
37        delete LocaleUtility_cache;
38        LocaleUtility_cache = NULL;
39    }
40    return TRUE;
41}
42U_CDECL_END
43
44U_NAMESPACE_BEGIN
45
46UnicodeString&
47LocaleUtility::canonicalLocaleString(const UnicodeString* id, UnicodeString& result)
48{
49  if (id == NULL) {
50    result.setToBogus();
51  } else {
52    // Fix case only (no other changes) up to the first '@' or '.' or
53    // end of string, whichever comes first.  In 3.0 I changed this to
54    // stop at first '@' or '.'.  It used to run out to the end of
55    // string.  My fix makes the tests pass but is probably
56    // structurally incorrect.  See below.  [alan 3.0]
57
58    // TODO: Doug, you might want to revise this...
59    result = *id;
60    int32_t i = 0;
61    int32_t end = result.indexOf(AT_SIGN_CHAR);
62    int32_t n = result.indexOf(PERIOD_CHAR);
63    if (n >= 0 && n < end) {
64        end = n;
65    }
66    if (end < 0) {
67        end = result.length();
68    }
69    n = result.indexOf(UNDERSCORE_CHAR);
70    if (n < 0) {
71      n = end;
72    }
73    for (; i < n; ++i) {
74      UChar c = result.charAt(i);
75      if (c >= 0x0041 && c <= 0x005a) {
76        c += 0x20;
77        result.setCharAt(i, c);
78      }
79    }
80    for (n = end; i < n; ++i) {
81      UChar c = result.charAt(i);
82      if (c >= 0x0061 && c <= 0x007a) {
83        c -= 0x20;
84        result.setCharAt(i, c);
85      }
86    }
87  }
88  return result;
89
90#if 0
91    // This code does a proper full level 2 canonicalization of id.
92    // It's nasty to go from UChar to char to char to UChar -- but
93    // that's what you have to do to use the uloc_canonicalize
94    // function on UnicodeStrings.
95
96    // I ended up doing the alternate fix (see above) not for
97    // performance reasons, although performance will certainly be
98    // better, but because doing a full level 2 canonicalization
99    // causes some tests to fail.  [alan 3.0]
100
101    // TODO: Doug, you might want to revisit this...
102    result.setToBogus();
103    if (id != 0) {
104        int32_t buflen = id->length() + 8; // space for NUL
105        char* buf = (char*) uprv_malloc(buflen);
106        char* canon = (buf == 0) ? 0 : (char*) uprv_malloc(buflen);
107        if (buf != 0 && canon != 0) {
108            U_ASSERT(id->extract(0, INT32_MAX, buf, buflen) < buflen);
109            UErrorCode ec = U_ZERO_ERROR;
110            uloc_canonicalize(buf, canon, buflen, &ec);
111            if (U_SUCCESS(ec)) {
112                result = UnicodeString(canon);
113            }
114        }
115        uprv_free(buf);
116        uprv_free(canon);
117    }
118    return result;
119#endif
120}
121
122Locale&
123LocaleUtility::initLocaleFromName(const UnicodeString& id, Locale& result)
124{
125    enum { BUFLEN = 128 }; // larger than ever needed
126
127    if (id.isBogus() || id.length() >= BUFLEN) {
128        result.setToBogus();
129    } else {
130        /*
131         * We need to convert from a UnicodeString to char * in order to
132         * create a Locale.
133         *
134         * Problem: Locale ID strings may contain '@' which is a variant
135         * character and cannot be handled by invariant-character conversion.
136         *
137         * Hack: Since ICU code can handle locale IDs with multiple encodings
138         * of '@' (at least for EBCDIC; it's not known to be a problem for
139         * ASCII-based systems),
140         * we use regular invariant-character conversion for everything else
141         * and manually convert U+0040 into a compiler-char-constant '@'.
142         * While this compilation-time constant may not match the runtime
143         * encoding of '@', it should be one of the encodings which ICU
144         * recognizes.
145         *
146         * There should be only at most one '@' in a locale ID.
147         */
148        char buffer[BUFLEN];
149        int32_t prev, i;
150        prev = 0;
151        for(;;) {
152            i = id.indexOf((UChar)0x40, prev);
153            if(i < 0) {
154                // no @ between prev and the rest of the string
155                id.extract(prev, INT32_MAX, buffer + prev, BUFLEN - prev, US_INV);
156                break; // done
157            } else {
158                // normal invariant-character conversion for text between @s
159                id.extract(prev, i - prev, buffer + prev, BUFLEN - prev, US_INV);
160                // manually "convert" U+0040 at id[i] into '@' at buffer[i]
161                buffer[i] = '@';
162                prev = i + 1;
163            }
164        }
165        result = Locale::createFromName(buffer);
166    }
167    return result;
168}
169
170UnicodeString&
171LocaleUtility::initNameFromLocale(const Locale& locale, UnicodeString& result)
172{
173    if (locale.isBogus()) {
174        result.setToBogus();
175    } else {
176        result.append(UnicodeString(locale.getName(), -1, US_INV));
177    }
178    return result;
179}
180
181const Hashtable*
182LocaleUtility::getAvailableLocaleNames(const UnicodeString& bundleID)
183{
184    // LocaleUtility_cache is a hash-of-hashes.  The top-level keys
185    // are path strings ('bundleID') passed to
186    // ures_openAvailableLocales.  The top-level values are
187    // second-level hashes.  The second-level keys are result strings
188    // from ures_openAvailableLocales.  The second-level values are
189    // garbage ((void*)1 or other random pointer).
190
191    UErrorCode status = U_ZERO_ERROR;
192    Hashtable* cache;
193    umtx_lock(NULL);
194    cache = LocaleUtility_cache;
195    umtx_unlock(NULL);
196
197    if (cache == NULL) {
198        cache = new Hashtable(status);
199        if (cache == NULL || U_FAILURE(status)) {
200            return NULL; // catastrophic failure; e.g. out of memory
201        }
202        cache->setValueDeleter(uhash_deleteHashtable);
203        Hashtable* h; // set this to final LocaleUtility_cache value
204        umtx_lock(NULL);
205        h = LocaleUtility_cache;
206        if (h == NULL) {
207            LocaleUtility_cache = h = cache;
208            cache = NULL;
209            ucln_common_registerCleanup(UCLN_COMMON_SERVICE, service_cleanup);
210        }
211        umtx_unlock(NULL);
212        if(cache != NULL) {
213          delete cache;
214        }
215        cache = h;
216    }
217
218    U_ASSERT(cache != NULL);
219
220    Hashtable* htp;
221    umtx_lock(NULL);
222    htp = (Hashtable*) cache->get(bundleID);
223    umtx_unlock(NULL);
224
225    if (htp == NULL) {
226        htp = new Hashtable(status);
227        if (htp && U_SUCCESS(status)) {
228            CharString cbundleID;
229            cbundleID.appendInvariantChars(bundleID, status);
230            const char* path = cbundleID.isEmpty() ? NULL : cbundleID.data();
231            UEnumeration *uenum = ures_openAvailableLocales(path, &status);
232            for (;;) {
233                const UChar* id = uenum_unext(uenum, NULL, &status);
234                if (id == NULL) {
235                    break;
236                }
237                htp->put(UnicodeString(id), (void*)htp, status);
238            }
239            uenum_close(uenum);
240            if (U_FAILURE(status)) {
241                delete htp;
242                return NULL;
243            }
244            umtx_lock(NULL);
245            cache->put(bundleID, (void*)htp, status);
246            umtx_unlock(NULL);
247        }
248    }
249    return htp;
250}
251
252UBool
253LocaleUtility::isFallbackOf(const UnicodeString& root, const UnicodeString& child)
254{
255    return child.indexOf(root) == 0 &&
256      (child.length() == root.length() ||
257       child.charAt(root.length()) == UNDERSCORE_CHAR);
258}
259
260U_NAMESPACE_END
261
262/* !UCONFIG_NO_SERVICE */
263#endif
264
265
266