1/*
2*******************************************************************************
3*
4*   Copyright (C) 2013-2014, 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 "simplepatternformatter.h"
19#include "mutex.h"
20#include "hash.h"
21#include "cstring.h"
22#include "ulocimp.h"
23#include "charstr.h"
24#include "ucln_cmn.h"
25#include "uresimp.h"
26
27U_NAMESPACE_BEGIN
28
29struct ListFormatInternal : public UMemory {
30    SimplePatternFormatter twoPattern;
31    SimplePatternFormatter startPattern;
32    SimplePatternFormatter middlePattern;
33    SimplePatternFormatter endPattern;
34
35ListFormatInternal(
36        const UnicodeString& two,
37        const UnicodeString& start,
38        const UnicodeString& middle,
39        const UnicodeString& end) :
40        twoPattern(two),
41        startPattern(start),
42        middlePattern(middle),
43        endPattern(end) {}
44
45ListFormatInternal(const ListFormatData &data) :
46        twoPattern(data.twoPattern),
47        startPattern(data.startPattern),
48        middlePattern(data.middlePattern),
49        endPattern(data.endPattern) { }
50
51ListFormatInternal(const ListFormatInternal &other) :
52    twoPattern(other.twoPattern),
53    startPattern(other.startPattern),
54    middlePattern(other.middlePattern),
55    endPattern(other.endPattern) { }
56};
57
58
59
60static Hashtable* listPatternHash = NULL;
61static UMutex listFormatterMutex = U_MUTEX_INITIALIZER;
62static const char *STANDARD_STYLE = "standard";
63
64U_CDECL_BEGIN
65static UBool U_CALLCONV uprv_listformatter_cleanup() {
66    delete listPatternHash;
67    listPatternHash = NULL;
68    return TRUE;
69}
70
71static void U_CALLCONV
72uprv_deleteListFormatInternal(void *obj) {
73    delete static_cast<ListFormatInternal *>(obj);
74}
75
76U_CDECL_END
77
78static ListFormatInternal* loadListFormatInternal(
79        const Locale& locale,
80        const char* style,
81        UErrorCode& errorCode);
82
83static void getStringByKey(
84        const UResourceBundle* rb,
85        const char* key,
86        UnicodeString& result,
87        UErrorCode& errorCode);
88
89ListFormatter::ListFormatter(const ListFormatter& other) :
90        owned(other.owned), data(other.data) {
91    if (other.owned != NULL) {
92        owned = new ListFormatInternal(*other.owned);
93        data = owned;
94    }
95}
96
97ListFormatter& ListFormatter::operator=(const ListFormatter& other) {
98    if (this == &other) {
99        return *this;
100    }
101    delete owned;
102    if (other.owned) {
103        owned = new ListFormatInternal(*other.owned);
104        data = owned;
105    } else {
106        owned = NULL;
107        data = other.data;
108    }
109    return *this;
110}
111
112void ListFormatter::initializeHash(UErrorCode& errorCode) {
113    if (U_FAILURE(errorCode)) {
114        return;
115    }
116
117    listPatternHash = new Hashtable();
118    if (listPatternHash == NULL) {
119        errorCode = U_MEMORY_ALLOCATION_ERROR;
120        return;
121    }
122
123    listPatternHash->setValueDeleter(uprv_deleteListFormatInternal);
124    ucln_common_registerCleanup(UCLN_COMMON_LIST_FORMATTER, uprv_listformatter_cleanup);
125
126}
127
128const ListFormatInternal* ListFormatter::getListFormatInternal(
129        const Locale& locale, const char *style, UErrorCode& errorCode) {
130    if (U_FAILURE(errorCode)) {
131        return NULL;
132    }
133    CharString keyBuffer(locale.getName(), errorCode);
134    keyBuffer.append(':', errorCode).append(style, errorCode);
135    UnicodeString key(keyBuffer.data(), -1, US_INV);
136    ListFormatInternal* result = NULL;
137    {
138        Mutex m(&listFormatterMutex);
139        if (listPatternHash == NULL) {
140            initializeHash(errorCode);
141            if (U_FAILURE(errorCode)) {
142                return NULL;
143            }
144        }
145        result = static_cast<ListFormatInternal*>(listPatternHash->get(key));
146    }
147    if (result != NULL) {
148        return result;
149    }
150    result = loadListFormatInternal(locale, style, errorCode);
151    if (U_FAILURE(errorCode)) {
152        return NULL;
153    }
154
155    {
156        Mutex m(&listFormatterMutex);
157        ListFormatInternal* temp = static_cast<ListFormatInternal*>(listPatternHash->get(key));
158        if (temp != NULL) {
159            delete result;
160            result = temp;
161        } else {
162            listPatternHash->put(key, result, errorCode);
163            if (U_FAILURE(errorCode)) {
164                return NULL;
165            }
166        }
167    }
168    return result;
169}
170
171static ListFormatInternal* loadListFormatInternal(
172        const Locale& locale, const char * style, UErrorCode& errorCode) {
173    UResourceBundle* rb = ures_open(NULL, locale.getName(), &errorCode);
174    if (U_FAILURE(errorCode)) {
175        ures_close(rb);
176        return NULL;
177    }
178    rb = ures_getByKeyWithFallback(rb, "listPattern", rb, &errorCode);
179    rb = ures_getByKeyWithFallback(rb, style, rb, &errorCode);
180
181    if (U_FAILURE(errorCode)) {
182        ures_close(rb);
183        return NULL;
184    }
185    UnicodeString two, start, middle, end;
186    getStringByKey(rb, "2", two, errorCode);
187    getStringByKey(rb, "start", start, errorCode);
188    getStringByKey(rb, "middle", middle, errorCode);
189    getStringByKey(rb, "end", end, errorCode);
190    ures_close(rb);
191    if (U_FAILURE(errorCode)) {
192        return NULL;
193    }
194    ListFormatInternal* result = new ListFormatInternal(two, start, middle, end);
195    if (result == NULL) {
196        errorCode = U_MEMORY_ALLOCATION_ERROR;
197        return NULL;
198    }
199    return result;
200}
201
202static void getStringByKey(const UResourceBundle* rb, const char* key, UnicodeString& result, UErrorCode& errorCode) {
203    int32_t len;
204    const UChar* ustr = ures_getStringByKeyWithFallback(rb, key, &len, &errorCode);
205    if (U_FAILURE(errorCode)) {
206      return;
207    }
208    result.setTo(ustr, len);
209}
210
211ListFormatter* ListFormatter::createInstance(UErrorCode& errorCode) {
212    Locale locale;  // The default locale.
213    return createInstance(locale, errorCode);
214}
215
216ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& errorCode) {
217    return createInstance(locale, STANDARD_STYLE, errorCode);
218}
219
220ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {
221    Locale tempLocale = locale;
222    const ListFormatInternal* listFormatInternal = getListFormatInternal(tempLocale, style, errorCode);
223    if (U_FAILURE(errorCode)) {
224        return NULL;
225    }
226    ListFormatter* p = new ListFormatter(listFormatInternal);
227    if (p == NULL) {
228        errorCode = U_MEMORY_ALLOCATION_ERROR;
229        return NULL;
230    }
231    return p;
232}
233
234ListFormatter::ListFormatter(const ListFormatData& listFormatData) {
235    owned = new ListFormatInternal(listFormatData);
236    data = owned;
237}
238
239ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(NULL), data(listFormatterInternal) {
240}
241
242ListFormatter::~ListFormatter() {
243    delete owned;
244}
245
246/**
247 * Joins first and second using the pattern pat.
248 * On entry offset is an offset into first or -1 if offset unspecified.
249 * On exit offset is offset of second in result if recordOffset was set
250 * Otherwise if it was >=0 it is set to point into result where it used
251 * to point into first. On exit, result is the join of first and second
252 * according to pat. Any previous value of result gets replaced.
253 */
254static void joinStringsAndReplace(
255        const SimplePatternFormatter& pat,
256        const UnicodeString& first,
257        const UnicodeString& second,
258        UnicodeString &result,
259        UBool recordOffset,
260        int32_t &offset,
261        UErrorCode& errorCode) {
262    if (U_FAILURE(errorCode)) {
263        return;
264    }
265    const UnicodeString *params[2] = {&first, &second};
266    int32_t offsets[2];
267    pat.formatAndReplace(
268            params,
269            UPRV_LENGTHOF(params),
270            result,
271            offsets,
272            UPRV_LENGTHOF(offsets),
273            errorCode);
274    if (U_FAILURE(errorCode)) {
275        return;
276    }
277    if (offsets[0] == -1 || offsets[1] == -1) {
278        errorCode = U_INVALID_FORMAT_ERROR;
279        return;
280    }
281    if (recordOffset) {
282        offset = offsets[1];
283    } else if (offset >= 0) {
284        offset += offsets[0];
285    }
286}
287
288UnicodeString& ListFormatter::format(
289        const UnicodeString items[],
290        int32_t nItems,
291        UnicodeString& appendTo,
292        UErrorCode& errorCode) const {
293    int32_t offset;
294    return format(items, nItems, appendTo, -1, offset, errorCode);
295}
296
297UnicodeString& ListFormatter::format(
298        const UnicodeString items[],
299        int32_t nItems,
300        UnicodeString& appendTo,
301        int32_t index,
302        int32_t &offset,
303        UErrorCode& errorCode) const {
304    offset = -1;
305    if (U_FAILURE(errorCode)) {
306        return appendTo;
307    }
308    if (data == NULL) {
309        errorCode = U_INVALID_STATE_ERROR;
310        return appendTo;
311    }
312
313    if (nItems <= 0) {
314        return appendTo;
315    }
316    if (nItems == 1) {
317        if (index == 0) {
318            offset = appendTo.length();
319        }
320        appendTo.append(items[0]);
321        return appendTo;
322    }
323    UnicodeString result(items[0]);
324    if (index == 0) {
325        offset = 0;
326    }
327    joinStringsAndReplace(
328            nItems == 2 ? data->twoPattern : data->startPattern,
329            result,
330            items[1],
331            result,
332            index == 1,
333            offset,
334            errorCode);
335    if (nItems > 2) {
336        for (int32_t i = 2; i < nItems - 1; ++i) {
337             joinStringsAndReplace(
338                     data->middlePattern,
339                     result,
340                     items[i],
341                     result,
342                     index == i,
343                     offset,
344                     errorCode);
345        }
346        joinStringsAndReplace(
347                data->endPattern,
348                result,
349                items[nItems - 1],
350                result,
351                index == nItems - 1,
352                offset,
353                errorCode);
354    }
355    if (U_SUCCESS(errorCode)) {
356        if (offset >= 0) {
357            offset += appendTo.length();
358        }
359        appendTo += result;
360    }
361    return appendTo;
362}
363
364U_NAMESPACE_END
365