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 U_NAMESPACE_BEGIN
28 
29 struct ListFormatInternal : public UMemory {
30     SimplePatternFormatter twoPattern;
31     SimplePatternFormatter startPattern;
32     SimplePatternFormatter middlePattern;
33     SimplePatternFormatter endPattern;
34 
ListFormatInternalListFormatInternal35 ListFormatInternal(
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 
ListFormatInternalListFormatInternal45 ListFormatInternal(const ListFormatData &data) :
46         twoPattern(data.twoPattern),
47         startPattern(data.startPattern),
48         middlePattern(data.middlePattern),
49         endPattern(data.endPattern) { }
50 
ListFormatInternalListFormatInternal51 ListFormatInternal(const ListFormatInternal &other) :
52     twoPattern(other.twoPattern),
53     startPattern(other.startPattern),
54     middlePattern(other.middlePattern),
55     endPattern(other.endPattern) { }
56 };
57 
58 
59 
60 static Hashtable* listPatternHash = NULL;
61 static UMutex listFormatterMutex = U_MUTEX_INITIALIZER;
62 static const char *STANDARD_STYLE = "standard";
63 
64 U_CDECL_BEGIN
uprv_listformatter_cleanup()65 static UBool U_CALLCONV uprv_listformatter_cleanup() {
66     delete listPatternHash;
67     listPatternHash = NULL;
68     return TRUE;
69 }
70 
71 static void U_CALLCONV
uprv_deleteListFormatInternal(void * obj)72 uprv_deleteListFormatInternal(void *obj) {
73     delete static_cast<ListFormatInternal *>(obj);
74 }
75 
76 U_CDECL_END
77 
78 static ListFormatInternal* loadListFormatInternal(
79         const Locale& locale,
80         const char* style,
81         UErrorCode& errorCode);
82 
83 static void getStringByKey(
84         const UResourceBundle* rb,
85         const char* key,
86         UnicodeString& result,
87         UErrorCode& errorCode);
88 
ListFormatter(const ListFormatter & other)89 ListFormatter::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 
operator =(const ListFormatter & other)97 ListFormatter& 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 
initializeHash(UErrorCode & errorCode)112 void 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 
getListFormatInternal(const Locale & locale,const char * style,UErrorCode & errorCode)128 const 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 
loadListFormatInternal(const Locale & locale,const char * style,UErrorCode & errorCode)171 static 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 
getStringByKey(const UResourceBundle * rb,const char * key,UnicodeString & result,UErrorCode & errorCode)202 static 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 
createInstance(UErrorCode & errorCode)211 ListFormatter* ListFormatter::createInstance(UErrorCode& errorCode) {
212     Locale locale;  // The default locale.
213     return createInstance(locale, errorCode);
214 }
215 
createInstance(const Locale & locale,UErrorCode & errorCode)216 ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& errorCode) {
217     return createInstance(locale, STANDARD_STYLE, errorCode);
218 }
219 
createInstance(const Locale & locale,const char * style,UErrorCode & errorCode)220 ListFormatter* 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 
ListFormatter(const ListFormatData & listFormatData)234 ListFormatter::ListFormatter(const ListFormatData& listFormatData) {
235     owned = new ListFormatInternal(listFormatData);
236     data = owned;
237 }
238 
ListFormatter(const ListFormatInternal * listFormatterInternal)239 ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(NULL), data(listFormatterInternal) {
240 }
241 
~ListFormatter()242 ListFormatter::~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  */
joinStringsAndReplace(const SimplePatternFormatter & pat,const UnicodeString & first,const UnicodeString & second,UnicodeString & result,UBool recordOffset,int32_t & offset,UErrorCode & errorCode)254 static 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 
format(const UnicodeString items[],int32_t nItems,UnicodeString & appendTo,UErrorCode & errorCode) const288 UnicodeString& 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 
format(const UnicodeString items[],int32_t nItems,UnicodeString & appendTo,int32_t index,int32_t & offset,UErrorCode & errorCode) const297 UnicodeString& 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 
364 U_NAMESPACE_END
365