1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "NativeDecimalFormat"
18
19#include "JNIHelp.h"
20#include "JniConstants.h"
21#include "JniException.h"
22#include "ScopedJavaUnicodeString.h"
23#include "ScopedPrimitiveArray.h"
24#include "ScopedStringChars.h"
25#include "ScopedUtfChars.h"
26#include "UniquePtr.h"
27#include "cutils/log.h"
28#include "digitlst.h"
29#include "unicode/decimfmt.h"
30#include "unicode/fmtable.h"
31#include "unicode/numfmt.h"
32#include "unicode/unum.h"
33#include "unicode/ustring.h"
34#include "valueOf.h"
35#include <stdlib.h>
36#include <string.h>
37
38static DecimalFormat* toDecimalFormat(jint addr) {
39    return reinterpret_cast<DecimalFormat*>(static_cast<uintptr_t>(addr));
40}
41
42static UNumberFormat* toUNumberFormat(jint addr) {
43    return reinterpret_cast<UNumberFormat*>(static_cast<uintptr_t>(addr));
44}
45
46static DecimalFormatSymbols* makeDecimalFormatSymbols(JNIEnv* env,
47        jstring currencySymbol0, jchar decimalSeparator, jchar digit, jstring exponentSeparator0,
48        jchar groupingSeparator0, jstring infinity0,
49        jstring internationalCurrencySymbol0, jchar minusSign,
50        jchar monetaryDecimalSeparator, jstring nan0, jchar patternSeparator,
51        jchar percent, jchar perMill, jchar zeroDigit) {
52    ScopedJavaUnicodeString currencySymbol(env, currencySymbol0);
53    ScopedJavaUnicodeString exponentSeparator(env, exponentSeparator0);
54    ScopedJavaUnicodeString infinity(env, infinity0);
55    ScopedJavaUnicodeString internationalCurrencySymbol(env, internationalCurrencySymbol0);
56    ScopedJavaUnicodeString nan(env, nan0);
57    UnicodeString groupingSeparator(groupingSeparator0);
58
59    DecimalFormatSymbols* result = new DecimalFormatSymbols;
60    result->setSymbol(DecimalFormatSymbols::kCurrencySymbol, currencySymbol.unicodeString());
61    result->setSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol, UnicodeString(decimalSeparator));
62    result->setSymbol(DecimalFormatSymbols::kDigitSymbol, UnicodeString(digit));
63    result->setSymbol(DecimalFormatSymbols::kExponentialSymbol, exponentSeparator.unicodeString());
64    result->setSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol, groupingSeparator);
65    result->setSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol, groupingSeparator);
66    result->setSymbol(DecimalFormatSymbols::kInfinitySymbol, infinity.unicodeString());
67    result->setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, internationalCurrencySymbol.unicodeString());
68    result->setSymbol(DecimalFormatSymbols::kMinusSignSymbol, UnicodeString(minusSign));
69    result->setSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol, UnicodeString(monetaryDecimalSeparator));
70    result->setSymbol(DecimalFormatSymbols::kNaNSymbol, nan.unicodeString());
71    result->setSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol, UnicodeString(patternSeparator));
72    result->setSymbol(DecimalFormatSymbols::kPercentSymbol, UnicodeString(percent));
73    result->setSymbol(DecimalFormatSymbols::kPerMillSymbol, UnicodeString(perMill));
74    // java.text.DecimalFormatSymbols just uses a zero digit,
75    // but ICU >= 4.6 has a field for each decimal digit.
76    result->setSymbol(DecimalFormatSymbols::kZeroDigitSymbol, UnicodeString(zeroDigit + 0));
77    result->setSymbol(DecimalFormatSymbols::kOneDigitSymbol, UnicodeString(zeroDigit + 1));
78    result->setSymbol(DecimalFormatSymbols::kTwoDigitSymbol, UnicodeString(zeroDigit + 2));
79    result->setSymbol(DecimalFormatSymbols::kThreeDigitSymbol, UnicodeString(zeroDigit + 3));
80    result->setSymbol(DecimalFormatSymbols::kFourDigitSymbol, UnicodeString(zeroDigit + 4));
81    result->setSymbol(DecimalFormatSymbols::kFiveDigitSymbol, UnicodeString(zeroDigit + 5));
82    result->setSymbol(DecimalFormatSymbols::kSixDigitSymbol, UnicodeString(zeroDigit + 6));
83    result->setSymbol(DecimalFormatSymbols::kSevenDigitSymbol, UnicodeString(zeroDigit + 7));
84    result->setSymbol(DecimalFormatSymbols::kEightDigitSymbol, UnicodeString(zeroDigit + 8));
85    result->setSymbol(DecimalFormatSymbols::kNineDigitSymbol, UnicodeString(zeroDigit + 9));
86    return result;
87}
88
89static void NativeDecimalFormat_setDecimalFormatSymbols(JNIEnv* env, jclass, jint addr,
90        jstring currencySymbol, jchar decimalSeparator, jchar digit, jstring exponentSeparator,
91        jchar groupingSeparator, jstring infinity,
92        jstring internationalCurrencySymbol, jchar minusSign,
93        jchar monetaryDecimalSeparator, jstring nan, jchar patternSeparator,
94        jchar percent, jchar perMill, jchar zeroDigit) {
95    DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env,
96            currencySymbol, decimalSeparator, digit, exponentSeparator, groupingSeparator,
97            infinity, internationalCurrencySymbol, minusSign,
98            monetaryDecimalSeparator, nan, patternSeparator, percent, perMill,
99            zeroDigit);
100    toDecimalFormat(addr)->adoptDecimalFormatSymbols(symbols);
101}
102
103static jint NativeDecimalFormat_open(JNIEnv* env, jclass, jstring pattern0,
104        jstring currencySymbol, jchar decimalSeparator, jchar digit, jstring exponentSeparator,
105        jchar groupingSeparator, jstring infinity,
106        jstring internationalCurrencySymbol, jchar minusSign,
107        jchar monetaryDecimalSeparator, jstring nan, jchar patternSeparator,
108        jchar percent, jchar perMill, jchar zeroDigit) {
109    if (pattern0 == NULL) {
110        jniThrowNullPointerException(env, NULL);
111        return 0;
112    }
113    UErrorCode status = U_ZERO_ERROR;
114    UParseError parseError;
115    ScopedJavaUnicodeString pattern(env, pattern0);
116    DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env,
117            currencySymbol, decimalSeparator, digit, exponentSeparator, groupingSeparator,
118            infinity, internationalCurrencySymbol, minusSign,
119            monetaryDecimalSeparator, nan, patternSeparator, percent, perMill,
120            zeroDigit);
121    DecimalFormat* fmt = new DecimalFormat(pattern.unicodeString(), symbols, parseError, status);
122    if (fmt == NULL) {
123        delete symbols;
124    }
125    maybeThrowIcuException(env, "DecimalFormat::DecimalFormat", status);
126    return static_cast<jint>(reinterpret_cast<uintptr_t>(fmt));
127}
128
129static void NativeDecimalFormat_close(JNIEnv*, jclass, jint addr) {
130    delete toDecimalFormat(addr);
131}
132
133static void NativeDecimalFormat_setRoundingMode(JNIEnv*, jclass, jint addr, jint mode, jdouble increment) {
134    DecimalFormat* fmt = toDecimalFormat(addr);
135    fmt->setRoundingMode(static_cast<DecimalFormat::ERoundingMode>(mode));
136    fmt->setRoundingIncrement(increment);
137}
138
139static void NativeDecimalFormat_setSymbol(JNIEnv* env, jclass, jint addr, jint javaSymbol, jstring javaValue) {
140    ScopedStringChars value(env, javaValue);
141    if (value.get() == NULL) {
142        return;
143    }
144    UErrorCode status = U_ZERO_ERROR;
145    UNumberFormatSymbol symbol = static_cast<UNumberFormatSymbol>(javaSymbol);
146    unum_setSymbol(toUNumberFormat(addr), symbol, value.get(), value.size(), &status);
147    maybeThrowIcuException(env, "unum_setSymbol", status);
148}
149
150static void NativeDecimalFormat_setAttribute(JNIEnv*, jclass, jint addr, jint javaAttr, jint value) {
151    UNumberFormatAttribute attr = static_cast<UNumberFormatAttribute>(javaAttr);
152    unum_setAttribute(toUNumberFormat(addr), attr, value);
153}
154
155static jint NativeDecimalFormat_getAttribute(JNIEnv*, jclass, jint addr, jint javaAttr) {
156    UNumberFormatAttribute attr = static_cast<UNumberFormatAttribute>(javaAttr);
157    return unum_getAttribute(toUNumberFormat(addr), attr);
158}
159
160static void NativeDecimalFormat_setTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr, jstring javaValue) {
161    ScopedStringChars value(env, javaValue);
162    if (value.get() == NULL) {
163        return;
164    }
165    UErrorCode status = U_ZERO_ERROR;
166    UNumberFormatTextAttribute attr = static_cast<UNumberFormatTextAttribute>(javaAttr);
167    unum_setTextAttribute(toUNumberFormat(addr), attr, value.get(), value.size(), &status);
168    maybeThrowIcuException(env, "unum_setTextAttribute", status);
169}
170
171static jstring NativeDecimalFormat_getTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr) {
172    UErrorCode status = U_ZERO_ERROR;
173    UNumberFormat* fmt = toUNumberFormat(addr);
174    UNumberFormatTextAttribute attr = static_cast<UNumberFormatTextAttribute>(javaAttr);
175
176    // Find out how long the result will be...
177    UniquePtr<UChar[]> chars;
178    uint32_t charCount = 0;
179    uint32_t desiredCount = unum_getTextAttribute(fmt, attr, chars.get(), charCount, &status);
180    if (status == U_BUFFER_OVERFLOW_ERROR) {
181        // ...then get it.
182        status = U_ZERO_ERROR;
183        charCount = desiredCount + 1;
184        chars.reset(new UChar[charCount]);
185        charCount = unum_getTextAttribute(fmt, attr, chars.get(), charCount, &status);
186    }
187    return maybeThrowIcuException(env, "unum_getTextAttribute", status) ? NULL : env->NewString(chars.get(), charCount);
188}
189
190static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized, jstring pattern0) {
191    if (pattern0 == NULL) {
192        jniThrowNullPointerException(env, NULL);
193        return;
194    }
195    ScopedJavaUnicodeString pattern(env, pattern0);
196    DecimalFormat* fmt = toDecimalFormat(addr);
197    UErrorCode status = U_ZERO_ERROR;
198    const char* function;
199    if (localized) {
200        function = "DecimalFormat::applyLocalizedPattern";
201        fmt->applyLocalizedPattern(pattern.unicodeString(), status);
202    } else {
203        function = "DecimalFormat::applyPattern";
204        fmt->applyPattern(pattern.unicodeString(), status);
205    }
206    maybeThrowIcuException(env, function, status);
207}
208
209static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized) {
210    DecimalFormat* fmt = toDecimalFormat(addr);
211    UnicodeString pattern;
212    if (localized) {
213        fmt->toLocalizedPattern(pattern);
214    } else {
215        fmt->toPattern(pattern);
216    }
217    return env->NewString(pattern.getBuffer(), pattern.length());
218}
219
220static jcharArray formatResult(JNIEnv* env, const UnicodeString &str, FieldPositionIterator* fpi, jobject fpIter) {
221    static jmethodID gFPI_setData = env->GetMethodID(JniConstants::fieldPositionIteratorClass, "setData", "([I)V");
222
223    if (fpi != NULL) {
224        int len = fpi->getData(NULL, 0);
225        jintArray data = NULL;
226        if (len) {
227            data = env->NewIntArray(len);
228            ScopedIntArrayRW ints(env, data);
229            if (ints.get() == NULL) {
230                return NULL;
231            }
232            fpi->getData(ints.get(), len);
233        }
234        env->CallVoidMethod(fpIter, gFPI_setData, data);
235    }
236
237    jcharArray result = env->NewCharArray(str.length());
238    if (result != NULL) {
239        env->SetCharArrayRegion(result, 0, str.length(), str.getBuffer());
240    }
241    return result;
242}
243
244template <typename T>
245static jcharArray format(JNIEnv* env, jint addr, jobject fpIter, T val) {
246    UErrorCode status = U_ZERO_ERROR;
247    UnicodeString str;
248    DecimalFormat* fmt = toDecimalFormat(addr);
249    FieldPositionIterator fpi;
250    FieldPositionIterator* pfpi = fpIter ? &fpi : NULL;
251    fmt->format(val, str, pfpi, status);
252    return formatResult(env, str, pfpi, fpIter);
253}
254
255static jcharArray NativeDecimalFormat_formatLong(JNIEnv* env, jclass, jint addr, jlong value, jobject fpIter) {
256    return format(env, addr, fpIter, value);
257}
258
259static jcharArray NativeDecimalFormat_formatDouble(JNIEnv* env, jclass, jint addr, jdouble value, jobject fpIter) {
260    return format(env, addr, fpIter, value);
261}
262
263static jcharArray NativeDecimalFormat_formatDigitList(JNIEnv* env, jclass, jint addr, jstring value, jobject fpIter) {
264    ScopedUtfChars chars(env, value);
265    if (chars.c_str() == NULL) {
266        return NULL;
267    }
268    StringPiece sp(chars.c_str());
269    return format(env, addr, fpIter, sp);
270}
271
272static jobject newBigDecimal(JNIEnv* env, const char* value, jsize len) {
273    static jmethodID gBigDecimal_init = env->GetMethodID(JniConstants::bigDecimalClass, "<init>", "(Ljava/lang/String;)V");
274
275    // this is painful...
276    // value is a UTF-8 string of invariant characters, but isn't guaranteed to be
277    // null-terminated.  NewStringUTF requires a terminated UTF-8 string.  So we copy the
278    // data to jchars using UnicodeString, and call NewString instead.
279    UnicodeString tmp(value, len, UnicodeString::kInvariant);
280    jobject str = env->NewString(tmp.getBuffer(), tmp.length());
281    return env->NewObject(JniConstants::bigDecimalClass, gBigDecimal_init, str);
282}
283
284static jobject NativeDecimalFormat_parse(JNIEnv* env, jclass, jint addr, jstring text,
285        jobject position, jboolean parseBigDecimal) {
286
287    static jmethodID gPP_getIndex = env->GetMethodID(JniConstants::parsePositionClass, "getIndex", "()I");
288    static jmethodID gPP_setIndex = env->GetMethodID(JniConstants::parsePositionClass, "setIndex", "(I)V");
289    static jmethodID gPP_setErrorIndex = env->GetMethodID(JniConstants::parsePositionClass, "setErrorIndex", "(I)V");
290
291    // make sure the ParsePosition is valid. Actually icu4c would parse a number
292    // correctly even if the parsePosition is set to -1, but since the RI fails
293    // for that case we have to fail too
294    int parsePos = env->CallIntMethod(position, gPP_getIndex, NULL);
295    if (parsePos < 0 || parsePos > env->GetStringLength(text)) {
296        return NULL;
297    }
298
299    Formattable res;
300    ParsePosition pp(parsePos);
301    ScopedJavaUnicodeString src(env, text);
302    DecimalFormat* fmt = toDecimalFormat(addr);
303    fmt->parse(src.unicodeString(), res, pp);
304
305    if (pp.getErrorIndex() == -1) {
306        env->CallVoidMethod(position, gPP_setIndex, pp.getIndex());
307    } else {
308        env->CallVoidMethod(position, gPP_setErrorIndex, pp.getErrorIndex());
309        return NULL;
310    }
311
312    if (parseBigDecimal) {
313        UErrorCode status = U_ZERO_ERROR;
314        StringPiece str = res.getDecimalNumber(status);
315        if (U_SUCCESS(status)) {
316            int len = str.length();
317            const char* data = str.data();
318            if (strncmp(data, "NaN", 3) == 0 ||
319                strncmp(data, "Inf", 3) == 0 ||
320                strncmp(data, "-Inf", 4) == 0) {
321                double resultDouble = res.getDouble(status);
322                return doubleValueOf(env, resultDouble);
323            }
324            return newBigDecimal(env, data, len);
325        }
326        return NULL;
327    }
328
329    switch (res.getType()) {
330        case Formattable::kDouble: return doubleValueOf(env, res.getDouble());
331        case Formattable::kLong:   return longValueOf(env, res.getLong());
332        case Formattable::kInt64:  return longValueOf(env, res.getInt64());
333        default:                   return NULL;
334    }
335}
336
337static jint NativeDecimalFormat_cloneImpl(JNIEnv*, jclass, jint addr) {
338    DecimalFormat* fmt = toDecimalFormat(addr);
339    return static_cast<jint>(reinterpret_cast<uintptr_t>(fmt->clone()));
340}
341
342static JNINativeMethod gMethods[] = {
343    NATIVE_METHOD(NativeDecimalFormat, applyPatternImpl, "(IZLjava/lang/String;)V"),
344    NATIVE_METHOD(NativeDecimalFormat, cloneImpl, "(I)I"),
345    NATIVE_METHOD(NativeDecimalFormat, close, "(I)V"),
346    NATIVE_METHOD(NativeDecimalFormat, formatDouble, "(IDLlibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
347    NATIVE_METHOD(NativeDecimalFormat, formatLong, "(IJLlibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
348    NATIVE_METHOD(NativeDecimalFormat, formatDigitList, "(ILjava/lang/String;Llibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
349    NATIVE_METHOD(NativeDecimalFormat, getAttribute, "(II)I"),
350    NATIVE_METHOD(NativeDecimalFormat, getTextAttribute, "(II)Ljava/lang/String;"),
351    NATIVE_METHOD(NativeDecimalFormat, open, "(Ljava/lang/String;Ljava/lang/String;CCLjava/lang/String;CLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)I"),
352    NATIVE_METHOD(NativeDecimalFormat, parse, "(ILjava/lang/String;Ljava/text/ParsePosition;Z)Ljava/lang/Number;"),
353    NATIVE_METHOD(NativeDecimalFormat, setAttribute, "(III)V"),
354    NATIVE_METHOD(NativeDecimalFormat, setDecimalFormatSymbols, "(ILjava/lang/String;CCLjava/lang/String;CLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)V"),
355    NATIVE_METHOD(NativeDecimalFormat, setRoundingMode, "(IID)V"),
356    NATIVE_METHOD(NativeDecimalFormat, setSymbol, "(IILjava/lang/String;)V"),
357    NATIVE_METHOD(NativeDecimalFormat, setTextAttribute, "(IILjava/lang/String;)V"),
358    NATIVE_METHOD(NativeDecimalFormat, toPatternImpl, "(IZ)Ljava/lang/String;"),
359};
360void register_libcore_icu_NativeDecimalFormat(JNIEnv* env) {
361    jniRegisterNativeMethods(env, "libcore/icu/NativeDecimalFormat", gMethods, NELEM(gMethods));
362}
363