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