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