1/*
2*******************************************************************************
3*
4*   Copyright (C) 2013, International Business Machines
5*   Corporation and others.  All Rights Reserved.
6*
7*******************************************************************************
8*   file name:  listformatter.cpp
9*   encoding:   US-ASCII
10*   tab size:   8 (not used)
11*   indentation:4
12*
13*   created on: 2012aug27
14*   created by: Umesh P. Nair
15*/
16
17#include "unicode/listformatter.h"
18#include "mutex.h"
19#include "hash.h"
20#include "cstring.h"
21#include "ulocimp.h"
22#include "charstr.h"
23#include "ucln_cmn.h"
24#include "uresimp.h"
25
26U_NAMESPACE_BEGIN
27
28static Hashtable* listPatternHash = NULL;
29static UMutex listFormatterMutex = U_MUTEX_INITIALIZER;
30static UChar FIRST_PARAMETER[] = { 0x7b, 0x30, 0x7d };  // "{0}"
31static UChar SECOND_PARAMETER[] = { 0x7b, 0x31, 0x7d };  // "{0}"
32static const char *STANDARD_STYLE = "standard";
33
34U_CDECL_BEGIN
35static UBool U_CALLCONV uprv_listformatter_cleanup() {
36    delete listPatternHash;
37    listPatternHash = NULL;
38    return TRUE;
39}
40
41static void U_CALLCONV
42uprv_deleteListFormatData(void *obj) {
43    delete static_cast<ListFormatData *>(obj);
44}
45
46U_CDECL_END
47
48static ListFormatData* loadListFormatData(const Locale& locale, const char* style, UErrorCode& errorCode);
49static void getStringByKey(const UResourceBundle* rb, const char* key, UnicodeString& result, UErrorCode& errorCode);
50
51ListFormatter::ListFormatter(const ListFormatter& other) : data(other.data) {
52}
53
54ListFormatter& ListFormatter::operator=(const ListFormatter& other) {
55    data = other.data;
56    return *this;
57}
58
59void ListFormatter::initializeHash(UErrorCode& errorCode) {
60    if (U_FAILURE(errorCode)) {
61        return;
62    }
63
64    listPatternHash = new Hashtable();
65    if (listPatternHash == NULL) {
66        errorCode = U_MEMORY_ALLOCATION_ERROR;
67        return;
68    }
69
70    listPatternHash->setValueDeleter(uprv_deleteListFormatData);
71    ucln_common_registerCleanup(UCLN_COMMON_LIST_FORMATTER, uprv_listformatter_cleanup);
72
73}
74
75const ListFormatData* ListFormatter::getListFormatData(
76        const Locale& locale, const char *style, UErrorCode& errorCode) {
77    if (U_FAILURE(errorCode)) {
78        return NULL;
79    }
80    CharString keyBuffer(locale.getName(), errorCode);
81    keyBuffer.append(':', errorCode).append(style, errorCode);
82    UnicodeString key(keyBuffer.data(), -1, US_INV);
83    ListFormatData* result = NULL;
84    {
85        Mutex m(&listFormatterMutex);
86        if (listPatternHash == NULL) {
87            initializeHash(errorCode);
88            if (U_FAILURE(errorCode)) {
89                return NULL;
90            }
91        }
92        result = static_cast<ListFormatData*>(listPatternHash->get(key));
93    }
94    if (result != NULL) {
95        return result;
96    }
97    result = loadListFormatData(locale, style, errorCode);
98    if (U_FAILURE(errorCode)) {
99        return NULL;
100    }
101
102    {
103        Mutex m(&listFormatterMutex);
104        ListFormatData* temp = static_cast<ListFormatData*>(listPatternHash->get(key));
105        if (temp != NULL) {
106            delete result;
107            result = temp;
108        } else {
109            listPatternHash->put(key, result, errorCode);
110            if (U_FAILURE(errorCode)) {
111                return NULL;
112            }
113        }
114    }
115    return result;
116}
117
118static ListFormatData* loadListFormatData(
119        const Locale& locale, const char * style, UErrorCode& errorCode) {
120    UResourceBundle* rb = ures_open(NULL, locale.getName(), &errorCode);
121    if (U_FAILURE(errorCode)) {
122        ures_close(rb);
123        return NULL;
124    }
125    rb = ures_getByKeyWithFallback(rb, "listPattern", rb, &errorCode);
126    rb = ures_getByKeyWithFallback(rb, style, rb, &errorCode);
127
128    // TODO(Travis Keep): This is a hack until fallbacks can be added for
129    // listPattern/duration and listPattern/duration-narrow in CLDR.
130    if (errorCode == U_MISSING_RESOURCE_ERROR) {
131        errorCode = U_ZERO_ERROR;
132        rb = ures_getByKeyWithFallback(rb, "standard", rb, &errorCode);
133    }
134    if (U_FAILURE(errorCode)) {
135        ures_close(rb);
136        return NULL;
137    }
138    UnicodeString two, start, middle, end;
139    getStringByKey(rb, "2", two, errorCode);
140    getStringByKey(rb, "start", start, errorCode);
141    getStringByKey(rb, "middle", middle, errorCode);
142    getStringByKey(rb, "end", end, errorCode);
143    ures_close(rb);
144    if (U_FAILURE(errorCode)) {
145        return NULL;
146    }
147    ListFormatData* result = new ListFormatData(two, start, middle, end);
148    if (result == NULL) {
149        errorCode = U_MEMORY_ALLOCATION_ERROR;
150        return NULL;
151    }
152    return result;
153}
154
155static void getStringByKey(const UResourceBundle* rb, const char* key, UnicodeString& result, UErrorCode& errorCode) {
156    int32_t len;
157    const UChar* ustr = ures_getStringByKeyWithFallback(rb, key, &len, &errorCode);
158    if (U_FAILURE(errorCode)) {
159      return;
160    }
161    result.setTo(ustr, len);
162}
163
164ListFormatter* ListFormatter::createInstance(UErrorCode& errorCode) {
165    Locale locale;  // The default locale.
166    return createInstance(locale, errorCode);
167}
168
169ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& errorCode) {
170    return createInstance(locale, STANDARD_STYLE, errorCode);
171}
172
173ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {
174    Locale tempLocale = locale;
175    const ListFormatData* listFormatData = getListFormatData(tempLocale, style, errorCode);
176    if (U_FAILURE(errorCode)) {
177        return NULL;
178    }
179    ListFormatter* p = new ListFormatter(listFormatData);
180    if (p == NULL) {
181        errorCode = U_MEMORY_ALLOCATION_ERROR;
182        return NULL;
183    }
184    return p;
185}
186
187
188ListFormatter::ListFormatter(const ListFormatData* listFormatterData) : data(listFormatterData) {
189}
190
191ListFormatter::~ListFormatter() {}
192
193UnicodeString& ListFormatter::format(const UnicodeString items[], int32_t nItems,
194                      UnicodeString& appendTo, UErrorCode& errorCode) const {
195    if (U_FAILURE(errorCode)) {
196        return appendTo;
197    }
198    if (data == NULL) {
199        errorCode = U_INVALID_STATE_ERROR;
200        return appendTo;
201    }
202
203    if (nItems > 0) {
204        UnicodeString newString = items[0];
205        if (nItems == 2) {
206            addNewString(data->twoPattern, newString, items[1], errorCode);
207        } else if (nItems > 2) {
208            addNewString(data->startPattern, newString, items[1], errorCode);
209            int32_t i;
210            for (i = 2; i < nItems - 1; ++i) {
211                addNewString(data->middlePattern, newString, items[i], errorCode);
212            }
213            addNewString(data->endPattern, newString, items[nItems - 1], errorCode);
214        }
215        if (U_SUCCESS(errorCode)) {
216            appendTo += newString;
217        }
218    }
219    return appendTo;
220}
221
222/**
223 * Joins originalString and nextString using the pattern pat and puts the result in
224 * originalString.
225 */
226void ListFormatter::addNewString(const UnicodeString& pat, UnicodeString& originalString,
227                                 const UnicodeString& nextString, UErrorCode& errorCode) const {
228    if (U_FAILURE(errorCode)) {
229        return;
230    }
231
232    int32_t p0Offset = pat.indexOf(FIRST_PARAMETER, 3, 0);
233    if (p0Offset < 0) {
234        errorCode = U_ILLEGAL_ARGUMENT_ERROR;
235        return;
236    }
237    int32_t p1Offset = pat.indexOf(SECOND_PARAMETER, 3, 0);
238    if (p1Offset < 0) {
239        errorCode = U_ILLEGAL_ARGUMENT_ERROR;
240        return;
241    }
242
243    int32_t i, j;
244
245    const UnicodeString* firstString;
246    const UnicodeString* secondString;
247    if (p0Offset < p1Offset) {
248        i = p0Offset;
249        j = p1Offset;
250        firstString = &originalString;
251        secondString = &nextString;
252    } else {
253        i = p1Offset;
254        j = p0Offset;
255        firstString = &nextString;
256        secondString = &originalString;
257    }
258
259    UnicodeString result = UnicodeString(pat, 0, i) + *firstString;
260    result += UnicodeString(pat, i+3, j-i-3);
261    result += *secondString;
262    result += UnicodeString(pat, j+3);
263    originalString = result;
264}
265
266U_NAMESPACE_END
267