1/*
2*******************************************************************************
3* Copyright (C) 2009, International Business Machines Corporation and
4* others. All Rights Reserved.
5*******************************************************************************
6*
7* File PLURFMT.CPP
8*
9* Modification History:
10*
11*   Date        Name        Description
12*******************************************************************************
13*/
14
15
16#include "unicode/utypes.h"
17#include "unicode/plurfmt.h"
18#include "unicode/plurrule.h"
19#include "plurrule_impl.h"
20
21#if !UCONFIG_NO_FORMATTING
22
23U_NAMESPACE_BEGIN
24
25U_CDECL_BEGIN
26static void U_CALLCONV
27deleteHashStrings(void *obj) {
28    delete (UnicodeString *)obj;
29}
30U_CDECL_END
31
32UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralFormat)
33
34#define MAX_KEYWORD_SIZE 30
35
36PluralFormat::PluralFormat(UErrorCode& status) {
37    init(NULL, Locale::getDefault(), status);
38}
39
40PluralFormat::PluralFormat(const Locale& loc, UErrorCode& status) {
41    init(NULL, loc, status);
42}
43
44PluralFormat::PluralFormat(const PluralRules& rules, UErrorCode& status) {
45    init(&rules, Locale::getDefault(), status);
46}
47
48PluralFormat::PluralFormat(const Locale& loc, const PluralRules& rules, UErrorCode& status) {
49    init(&rules, loc, status);
50}
51
52PluralFormat::PluralFormat(const UnicodeString& pat, UErrorCode& status) {
53    init(NULL, Locale::getDefault(), status);
54    applyPattern(pat, status);
55}
56
57PluralFormat::PluralFormat(const Locale& loc, const UnicodeString& pat, UErrorCode& status) {
58    init(NULL, loc, status);
59    applyPattern(pat, status);
60}
61
62PluralFormat::PluralFormat(const PluralRules& rules, const UnicodeString& pat, UErrorCode& status) {
63    init(&rules, Locale::getDefault(), status);
64    applyPattern(pat, status);
65}
66
67PluralFormat::PluralFormat(const Locale& loc, const PluralRules& rules, const UnicodeString& pat, UErrorCode& status) {
68    init(&rules, loc, status);
69    applyPattern(pat, status);
70}
71
72PluralFormat::PluralFormat(const PluralFormat& other) : Format(other) {
73    UErrorCode status = U_ZERO_ERROR;
74    locale = other.locale;
75    pluralRules = other.pluralRules->clone();
76    pattern = other.pattern;
77    copyHashtable(other.fParsedValuesHash, status);
78    if (U_FAILURE(status)) {
79        delete pluralRules;
80        pluralRules = NULL;
81        return;
82    }
83    numberFormat=NumberFormat::createInstance(locale, status);
84    if (U_FAILURE(status)) {
85        delete pluralRules;
86        pluralRules = NULL;
87        delete fParsedValuesHash;
88        fParsedValuesHash = NULL;
89        return;
90    }
91    replacedNumberFormat=other.replacedNumberFormat;
92}
93
94PluralFormat::~PluralFormat() {
95    delete pluralRules;
96    delete fParsedValuesHash;
97    delete numberFormat;
98}
99
100void
101PluralFormat::init(const PluralRules* rules, const Locale& curLocale, UErrorCode& status) {
102    if (U_FAILURE(status)) {
103        return;
104    }
105    locale = curLocale;
106    if ( rules==NULL) {
107        pluralRules = PluralRules::forLocale(locale, status);
108        if (U_FAILURE(status)) {
109            return;
110        }
111    }
112    else {
113        pluralRules = rules->clone();
114    }
115    fParsedValuesHash=NULL;
116    pattern.remove();
117    numberFormat= NumberFormat::createInstance(curLocale, status);
118    if (U_FAILURE(status)) {
119        delete pluralRules;
120        pluralRules = NULL;
121        return;
122    }
123    replacedNumberFormat=NULL;
124}
125
126void
127PluralFormat::applyPattern(const UnicodeString& newPattern, UErrorCode& status) {
128    if (U_FAILURE(status)) {
129        return;
130    }
131    this->pattern = newPattern;
132    UnicodeString token;
133    int32_t braceCount=0;
134    fmtToken type;
135    UBool spaceIncluded=FALSE;
136
137    if (fParsedValuesHash==NULL) {
138        fParsedValuesHash = new Hashtable(TRUE, status);
139        if (U_FAILURE(status)) {
140            return;
141        }
142        fParsedValuesHash->setValueDeleter(deleteHashStrings);
143    }
144
145    UBool getKeyword=TRUE;
146    UnicodeString hashKeyword;
147    UnicodeString *hashPattern;
148
149    for (int32_t i=0; i<pattern.length(); ++i) {
150        UChar ch=pattern.charAt(i);
151
152        if ( !inRange(ch, type) ) {
153            if (getKeyword) {
154                status = U_ILLEGAL_CHARACTER;
155                return;
156            }
157            else {
158                token += ch;
159                continue;
160            }
161        }
162        switch (type) {
163            case tSpace:
164                if (token.length()==0) {
165                    continue;
166                }
167                if (getKeyword) {
168                    // space after keyword
169                    spaceIncluded = TRUE;
170                }
171                else {
172                    token += ch;
173                }
174                break;
175            case tLeftBrace:
176                if ( getKeyword ) {
177                    if (fParsedValuesHash->get(token)!= NULL) {
178                        status = U_DUPLICATE_KEYWORD;
179                        return;
180                    }
181                    if (token.length()==0) {
182                        status = U_PATTERN_SYNTAX_ERROR;
183                        return;
184                    }
185                    if (!pluralRules->isKeyword(token)) {
186                        status = U_UNDEFINED_KEYWORD;
187                        return;
188                    }
189                    hashKeyword = token;
190                    getKeyword = FALSE;
191                    token.remove();
192                }
193                else  {
194                    if (braceCount==0) {
195                        status = U_UNEXPECTED_TOKEN;
196                        return;
197                    }
198                    else {
199                        token += ch;
200                    }
201                }
202                braceCount++;
203                spaceIncluded = FALSE;
204                break;
205            case tRightBrace:
206                if ( getKeyword ) {
207                    status = U_UNEXPECTED_TOKEN;
208                    return;
209                }
210                else  {
211                    hashPattern = new UnicodeString(token);
212                    fParsedValuesHash->put(hashKeyword, hashPattern, status);
213                    if (U_FAILURE(status)) {
214                        return;
215                    }
216                    braceCount--;
217                    if ( braceCount==0 ) {
218                        getKeyword=TRUE;
219                        hashKeyword.remove();
220                        hashPattern=NULL;
221                        token.remove();
222                    }
223                    else {
224                        token += ch;
225                    }
226                }
227                spaceIncluded = FALSE;
228                break;
229            case tLetter:
230            case tNumberSign:
231                if (spaceIncluded) {
232                    status = U_PATTERN_SYNTAX_ERROR;
233                    return;
234                }
235            default:
236                token+=ch;
237                break;
238        }
239    }
240    if ( checkSufficientDefinition() ) {
241        return;
242    }
243    else {
244        status = U_DEFAULT_KEYWORD_MISSING;
245        return;
246    }
247}
248
249UnicodeString&
250PluralFormat::format(const Formattable& obj,
251                   UnicodeString& appendTo,
252                   FieldPosition& pos,
253                   UErrorCode& status) const
254{
255    if (U_FAILURE(status)) return appendTo;
256    int32_t number;
257
258    switch (obj.getType())
259    {
260    case Formattable::kDouble:
261        return format((int32_t)obj.getDouble(), appendTo, pos, status);
262        break;
263    case Formattable::kLong:
264        number = (int32_t)obj.getLong();
265        return format(number, appendTo, pos, status);
266        break;
267    case Formattable::kInt64:
268        return format((int32_t)obj.getInt64(), appendTo, pos, status);
269    default:
270        status = U_ILLEGAL_ARGUMENT_ERROR;
271        return appendTo;
272    }
273}
274
275UnicodeString
276PluralFormat::format(int32_t number, UErrorCode& status) const {
277    if (U_FAILURE(status)) {
278        return UnicodeString();
279    }
280    FieldPosition fpos(0);
281    UnicodeString result;
282
283    return format(number, result, fpos, status);
284}
285
286UnicodeString
287PluralFormat::format(double number, UErrorCode& status) const {
288    if (U_FAILURE(status)) {
289        return UnicodeString();
290    }
291    FieldPosition fpos(0);
292    UnicodeString result;
293
294    return format(number, result, fpos, status);
295}
296
297
298UnicodeString&
299PluralFormat::format(int32_t number,
300                     UnicodeString& appendTo,
301                     FieldPosition& pos,
302                     UErrorCode& status) const {
303    return format((double)number, appendTo, pos, status);
304}
305
306UnicodeString&
307PluralFormat::format(double number,
308                     UnicodeString& appendTo,
309                     FieldPosition& pos,
310                     UErrorCode& /*status*/) const {
311
312    if (fParsedValuesHash==NULL) {
313        if ( replacedNumberFormat== NULL ) {
314            return numberFormat->format(number, appendTo, pos);
315        }
316        else {
317            replacedNumberFormat->format(number, appendTo, pos);
318        }
319    }
320    UnicodeString selectedRule = pluralRules->select(number);
321    UnicodeString *selectedPattern = (UnicodeString *)fParsedValuesHash->get(selectedRule);
322    if (selectedPattern==NULL) {
323        selectedPattern = (UnicodeString *)fParsedValuesHash->get(pluralRules->getKeywordOther());
324    }
325    appendTo = insertFormattedNumber(number, *selectedPattern, appendTo, pos);
326
327    return appendTo;
328}
329
330UnicodeString&
331PluralFormat::toPattern(UnicodeString& appendTo) {
332    appendTo+= pattern;
333    return appendTo;
334}
335
336UBool
337PluralFormat::inRange(UChar ch, fmtToken& type) {
338    if ((ch>=CAP_A) && (ch<=CAP_Z)) {
339        // we assume all characters are in lower case already.
340        return FALSE;
341    }
342    if ((ch>=LOW_A) && (ch<=LOW_Z)) {
343        type = tLetter;
344        return TRUE;
345    }
346    switch (ch) {
347        case LEFTBRACE:
348            type = tLeftBrace;
349            return TRUE;
350        case SPACE:
351            type = tSpace;
352            return TRUE;
353        case RIGHTBRACE:
354            type = tRightBrace;
355            return TRUE;
356        case NUMBER_SIGN:
357            type = tNumberSign;
358            return TRUE;
359        default :
360            type = none;
361            return FALSE;
362    }
363}
364
365UBool
366PluralFormat::checkSufficientDefinition() {
367    // Check that at least the default rule is defined.
368    if (fParsedValuesHash==NULL)  return FALSE;
369    if (fParsedValuesHash->get(pluralRules->getKeywordOther()) == NULL) {
370        return FALSE;
371    }
372    else {
373        return TRUE;
374    }
375}
376
377void
378PluralFormat::setLocale(const Locale& loc, UErrorCode& status) {
379    if (U_FAILURE(status)) {
380        return;
381    }
382    if (pluralRules!=NULL) {
383        delete pluralRules;
384        pluralRules=NULL;
385    }
386    if (fParsedValuesHash!= NULL) {
387        delete fParsedValuesHash;
388        fParsedValuesHash = NULL;
389    }
390    if (numberFormat!=NULL) {
391        delete numberFormat;
392        numberFormat = NULL;
393        replacedNumberFormat=NULL;
394    }
395    init(NULL, loc, status);
396}
397
398void
399PluralFormat::setNumberFormat(const NumberFormat* format, UErrorCode& /*status*/) {
400    // TODO: The copy constructor and assignment op of NumberFormat class are protected.
401    // create a pointer as the workaround.
402    replacedNumberFormat = (NumberFormat *)format;
403}
404
405Format*
406PluralFormat::clone() const
407{
408    return new PluralFormat(*this);
409}
410
411PluralFormat&
412PluralFormat::operator=(const PluralFormat& other) {
413    if (this != &other) {
414        UErrorCode status = U_ZERO_ERROR;
415        delete pluralRules;
416        delete fParsedValuesHash;
417        delete numberFormat;
418        locale = other.locale;
419        pluralRules = other.pluralRules->clone();
420        pattern = other.pattern;
421        copyHashtable(other.fParsedValuesHash, status);
422        if (U_FAILURE(status)) {
423            delete pluralRules;
424            pluralRules = NULL;
425            fParsedValuesHash = NULL;
426            numberFormat = NULL;
427            return *this;
428        }
429        numberFormat=NumberFormat::createInstance(locale, status);
430        if (U_FAILURE(status)) {
431            delete pluralRules;
432            delete fParsedValuesHash;
433            pluralRules = NULL;
434            fParsedValuesHash = NULL;
435            numberFormat = NULL;
436            return *this;
437        }
438        replacedNumberFormat=other.replacedNumberFormat;
439    }
440
441    return *this;
442}
443
444UBool
445PluralFormat::operator==(const Format& other) const {
446    // This protected comparison operator should only be called by subclasses
447    // which have confirmed that the other object being compared against is
448    // an instance of a sublcass of PluralFormat.  THIS IS IMPORTANT.
449    // Format::operator== guarantees that this cast is safe
450    PluralFormat* fmt = (PluralFormat*)&other;
451    return ((*pluralRules == *(fmt->pluralRules)) &&
452            (*numberFormat == *(fmt->numberFormat)));
453}
454
455UBool
456PluralFormat::operator!=(const Format& other) const {
457    return  !operator==(other);
458}
459
460void
461PluralFormat::parseObject(const UnicodeString& /*source*/,
462                        Formattable& /*result*/,
463                        ParsePosition& /*pos*/) const
464{
465    // TODO: not yet supported in icu4j and icu4c
466}
467
468UnicodeString
469PluralFormat::insertFormattedNumber(double number,
470                                    UnicodeString& message,
471                                    UnicodeString& appendTo,
472                                    FieldPosition& pos) const {
473    UnicodeString result;
474    int32_t braceStack=0;
475    int32_t startIndex=0;
476
477    if (message.length()==0) {
478        return result;
479    }
480    appendTo = numberFormat->format(number, appendTo, pos);
481    for(int32_t i=0; i<message.length(); ++i) {
482        switch(message.charAt(i)) {
483        case LEFTBRACE:
484            ++braceStack;
485            break;
486        case RIGHTBRACE:
487            --braceStack;
488            break;
489        case NUMBER_SIGN:
490            if (braceStack==0) {
491                result += UnicodeString(message, startIndex, i);
492                result += appendTo;
493                startIndex = i + 1;
494            }
495            break;
496        }
497    }
498    if ( startIndex < message.length() ) {
499        result += UnicodeString(message, startIndex, message.length()-startIndex);
500    }
501    appendTo = result;
502    return result;
503}
504
505void
506PluralFormat::copyHashtable(Hashtable *other, UErrorCode& status) {
507    if (other == NULL || U_FAILURE(status)) {
508        fParsedValuesHash = NULL;
509        return;
510    }
511    fParsedValuesHash = new Hashtable(TRUE, status);
512    if(U_FAILURE(status)){
513        return;
514    }
515    fParsedValuesHash->setValueDeleter(deleteHashStrings);
516    int32_t pos = -1;
517    const UHashElement* elem = NULL;
518    // walk through the hash table and create a deep clone
519    while((elem = other->nextElement(pos))!= NULL){
520        const UHashTok otherKeyTok = elem->key;
521        UnicodeString* otherKey = (UnicodeString*)otherKeyTok.pointer;
522        const UHashTok otherKeyToVal = elem->value;
523        UnicodeString* otherValue = (UnicodeString*)otherKeyToVal.pointer;
524        fParsedValuesHash->put(*otherKey, new UnicodeString(*otherValue), status);
525        if(U_FAILURE(status)){
526            return;
527        }
528    }
529}
530
531
532U_NAMESPACE_END
533
534
535#endif /* #if !UCONFIG_NO_FORMATTING */
536
537//eof
538