1 // Copyright (C) 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 *
6 *   Copyright (C) 2013-2016, International Business Machines
7 *   Corporation and others.  All Rights Reserved.
8 *
9 *******************************************************************************
10 *   file name:  listformatter.cpp
11 *   encoding:   US-ASCII
12 *   tab size:   8 (not used)
13 *   indentation:4
14 *
15 *   created on: 2012aug27
16 *   created by: Umesh P. Nair
17 */
18 
19 #include "unicode/listformatter.h"
20 #include "unicode/simpleformatter.h"
21 #include "mutex.h"
22 #include "hash.h"
23 #include "cstring.h"
24 #include "ulocimp.h"
25 #include "charstr.h"
26 #include "ucln_cmn.h"
27 #include "uresimp.h"
28 
29 U_NAMESPACE_BEGIN
30 
31 struct ListFormatInternal : public UMemory {
32     SimpleFormatter twoPattern;
33     SimpleFormatter startPattern;
34     SimpleFormatter middlePattern;
35     SimpleFormatter endPattern;
36 
ListFormatInternalListFormatInternal37 ListFormatInternal(
38         const UnicodeString& two,
39         const UnicodeString& start,
40         const UnicodeString& middle,
41         const UnicodeString& end,
42         UErrorCode &errorCode) :
43         twoPattern(two, 2, 2, errorCode),
44         startPattern(start, 2, 2, errorCode),
45         middlePattern(middle, 2, 2, errorCode),
46         endPattern(end, 2, 2, errorCode) {}
47 
ListFormatInternalListFormatInternal48 ListFormatInternal(const ListFormatData &data, UErrorCode &errorCode) :
49         twoPattern(data.twoPattern, errorCode),
50         startPattern(data.startPattern, errorCode),
51         middlePattern(data.middlePattern, errorCode),
52         endPattern(data.endPattern, errorCode) { }
53 
ListFormatInternalListFormatInternal54 ListFormatInternal(const ListFormatInternal &other) :
55     twoPattern(other.twoPattern),
56     startPattern(other.startPattern),
57     middlePattern(other.middlePattern),
58     endPattern(other.endPattern) { }
59 };
60 
61 
62 
63 static Hashtable* listPatternHash = NULL;
64 static UMutex listFormatterMutex = U_MUTEX_INITIALIZER;
65 static const char *STANDARD_STYLE = "standard";
66 
67 U_CDECL_BEGIN
uprv_listformatter_cleanup()68 static UBool U_CALLCONV uprv_listformatter_cleanup() {
69     delete listPatternHash;
70     listPatternHash = NULL;
71     return TRUE;
72 }
73 
74 static void U_CALLCONV
uprv_deleteListFormatInternal(void * obj)75 uprv_deleteListFormatInternal(void *obj) {
76     delete static_cast<ListFormatInternal *>(obj);
77 }
78 
79 U_CDECL_END
80 
81 static ListFormatInternal* loadListFormatInternal(
82         const Locale& locale,
83         const char* style,
84         UErrorCode& errorCode);
85 
86 static void getStringByKey(
87         const UResourceBundle* rb,
88         const char* key,
89         UnicodeString& result,
90         UErrorCode& errorCode);
91 
ListFormatter(const ListFormatter & other)92 ListFormatter::ListFormatter(const ListFormatter& other) :
93         owned(other.owned), data(other.data) {
94     if (other.owned != NULL) {
95         owned = new ListFormatInternal(*other.owned);
96         data = owned;
97     }
98 }
99 
operator =(const ListFormatter & other)100 ListFormatter& ListFormatter::operator=(const ListFormatter& other) {
101     if (this == &other) {
102         return *this;
103     }
104     delete owned;
105     if (other.owned) {
106         owned = new ListFormatInternal(*other.owned);
107         data = owned;
108     } else {
109         owned = NULL;
110         data = other.data;
111     }
112     return *this;
113 }
114 
initializeHash(UErrorCode & errorCode)115 void ListFormatter::initializeHash(UErrorCode& errorCode) {
116     if (U_FAILURE(errorCode)) {
117         return;
118     }
119 
120     listPatternHash = new Hashtable();
121     if (listPatternHash == NULL) {
122         errorCode = U_MEMORY_ALLOCATION_ERROR;
123         return;
124     }
125 
126     listPatternHash->setValueDeleter(uprv_deleteListFormatInternal);
127     ucln_common_registerCleanup(UCLN_COMMON_LIST_FORMATTER, uprv_listformatter_cleanup);
128 
129 }
130 
getListFormatInternal(const Locale & locale,const char * style,UErrorCode & errorCode)131 const ListFormatInternal* ListFormatter::getListFormatInternal(
132         const Locale& locale, const char *style, UErrorCode& errorCode) {
133     if (U_FAILURE(errorCode)) {
134         return NULL;
135     }
136     CharString keyBuffer(locale.getName(), errorCode);
137     keyBuffer.append(':', errorCode).append(style, errorCode);
138     UnicodeString key(keyBuffer.data(), -1, US_INV);
139     ListFormatInternal* result = NULL;
140     {
141         Mutex m(&listFormatterMutex);
142         if (listPatternHash == NULL) {
143             initializeHash(errorCode);
144             if (U_FAILURE(errorCode)) {
145                 return NULL;
146             }
147         }
148         result = static_cast<ListFormatInternal*>(listPatternHash->get(key));
149     }
150     if (result != NULL) {
151         return result;
152     }
153     result = loadListFormatInternal(locale, style, errorCode);
154     if (U_FAILURE(errorCode)) {
155         return NULL;
156     }
157 
158     {
159         Mutex m(&listFormatterMutex);
160         ListFormatInternal* temp = static_cast<ListFormatInternal*>(listPatternHash->get(key));
161         if (temp != NULL) {
162             delete result;
163             result = temp;
164         } else {
165             listPatternHash->put(key, result, errorCode);
166             if (U_FAILURE(errorCode)) {
167                 return NULL;
168             }
169         }
170     }
171     return result;
172 }
173 
loadListFormatInternal(const Locale & locale,const char * style,UErrorCode & errorCode)174 static ListFormatInternal* loadListFormatInternal(
175         const Locale& locale, const char * style, UErrorCode& errorCode) {
176     UResourceBundle* rb = ures_open(NULL, locale.getName(), &errorCode);
177     if (U_FAILURE(errorCode)) {
178         ures_close(rb);
179         return NULL;
180     }
181     rb = ures_getByKeyWithFallback(rb, "listPattern", rb, &errorCode);
182     rb = ures_getByKeyWithFallback(rb, style, rb, &errorCode);
183 
184     if (U_FAILURE(errorCode)) {
185         ures_close(rb);
186         return NULL;
187     }
188     UnicodeString two, start, middle, end;
189     getStringByKey(rb, "2", two, errorCode);
190     getStringByKey(rb, "start", start, errorCode);
191     getStringByKey(rb, "middle", middle, errorCode);
192     getStringByKey(rb, "end", end, errorCode);
193     ures_close(rb);
194     if (U_FAILURE(errorCode)) {
195         return NULL;
196     }
197     ListFormatInternal* result = new ListFormatInternal(two, start, middle, end, errorCode);
198     if (result == NULL) {
199         errorCode = U_MEMORY_ALLOCATION_ERROR;
200         return NULL;
201     }
202     if (U_FAILURE(errorCode)) {
203         delete result;
204         return NULL;
205     }
206     return result;
207 }
208 
getStringByKey(const UResourceBundle * rb,const char * key,UnicodeString & result,UErrorCode & errorCode)209 static void getStringByKey(const UResourceBundle* rb, const char* key, UnicodeString& result, UErrorCode& errorCode) {
210     int32_t len;
211     const UChar* ustr = ures_getStringByKeyWithFallback(rb, key, &len, &errorCode);
212     if (U_FAILURE(errorCode)) {
213       return;
214     }
215     result.setTo(ustr, len);
216 }
217 
createInstance(UErrorCode & errorCode)218 ListFormatter* ListFormatter::createInstance(UErrorCode& errorCode) {
219     Locale locale;  // The default locale.
220     return createInstance(locale, errorCode);
221 }
222 
createInstance(const Locale & locale,UErrorCode & errorCode)223 ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& errorCode) {
224     return createInstance(locale, STANDARD_STYLE, errorCode);
225 }
226 
createInstance(const Locale & locale,const char * style,UErrorCode & errorCode)227 ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {
228     Locale tempLocale = locale;
229     const ListFormatInternal* listFormatInternal = getListFormatInternal(tempLocale, style, errorCode);
230     if (U_FAILURE(errorCode)) {
231         return NULL;
232     }
233     ListFormatter* p = new ListFormatter(listFormatInternal);
234     if (p == NULL) {
235         errorCode = U_MEMORY_ALLOCATION_ERROR;
236         return NULL;
237     }
238     return p;
239 }
240 
ListFormatter(const ListFormatData & listFormatData,UErrorCode & errorCode)241 ListFormatter::ListFormatter(const ListFormatData& listFormatData, UErrorCode &errorCode) {
242     owned = new ListFormatInternal(listFormatData, errorCode);
243     data = owned;
244 }
245 
ListFormatter(const ListFormatInternal * listFormatterInternal)246 ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(NULL), data(listFormatterInternal) {
247 }
248 
~ListFormatter()249 ListFormatter::~ListFormatter() {
250     delete owned;
251 }
252 
253 /**
254  * Joins first and second using the pattern pat.
255  * On entry offset is an offset into first or -1 if offset unspecified.
256  * On exit offset is offset of second in result if recordOffset was set
257  * Otherwise if it was >=0 it is set to point into result where it used
258  * to point into first. On exit, result is the join of first and second
259  * according to pat. Any previous value of result gets replaced.
260  */
joinStringsAndReplace(const SimpleFormatter & pat,const UnicodeString & first,const UnicodeString & second,UnicodeString & result,UBool recordOffset,int32_t & offset,UErrorCode & errorCode)261 static void joinStringsAndReplace(
262         const SimpleFormatter& 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.formatAndReplace(
275             params,
276             UPRV_LENGTHOF(params),
277             result,
278             offsets,
279             UPRV_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 
format(const UnicodeString items[],int32_t nItems,UnicodeString & appendTo,UErrorCode & errorCode) const295 UnicodeString& 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 
format(const UnicodeString items[],int32_t nItems,UnicodeString & appendTo,int32_t index,int32_t & offset,UErrorCode & errorCode) const304 UnicodeString& 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     UnicodeString result(items[0]);
331     if (index == 0) {
332         offset = 0;
333     }
334     joinStringsAndReplace(
335             nItems == 2 ? data->twoPattern : data->startPattern,
336             result,
337             items[1],
338             result,
339             index == 1,
340             offset,
341             errorCode);
342     if (nItems > 2) {
343         for (int32_t i = 2; i < nItems - 1; ++i) {
344              joinStringsAndReplace(
345                      data->middlePattern,
346                      result,
347                      items[i],
348                      result,
349                      index == i,
350                      offset,
351                      errorCode);
352         }
353         joinStringsAndReplace(
354                 data->endPattern,
355                 result,
356                 items[nItems - 1],
357                 result,
358                 index == nItems - 1,
359                 offset,
360                 errorCode);
361     }
362     if (U_SUCCESS(errorCode)) {
363         if (offset >= 0) {
364             offset += appendTo.length();
365         }
366         appendTo += result;
367     }
368     return appendTo;
369 }
370 
371 U_NAMESPACE_END
372