/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "NativeDecimalFormat" #include "JNIHelp.h" #include "AndroidSystemNatives.h" #include "cutils/log.h" #include "unicode/unum.h" #include "unicode/numfmt.h" #include "unicode/decimfmt.h" #include "unicode/fmtable.h" #include "unicode/ustring.h" #include "digitlst.h" #include "ErrorCode.h" #include "ScopedJavaUnicodeString.h" #include #include static DecimalFormat* toDecimalFormat(jint addr) { return reinterpret_cast(static_cast(addr)); } static DecimalFormatSymbols* makeDecimalFormatSymbols(JNIEnv* env, jstring currencySymbol0, jchar decimalSeparator, jchar digit, jchar groupingSeparator0, jstring infinity0, jstring internationalCurrencySymbol0, jchar minusSign, jchar monetaryDecimalSeparator, jstring nan0, jchar patternSeparator, jchar percent, jchar perMill, jchar zeroDigit) { ScopedJavaUnicodeString currencySymbol(env, currencySymbol0); ScopedJavaUnicodeString infinity(env, infinity0); ScopedJavaUnicodeString internationalCurrencySymbol(env, internationalCurrencySymbol0); ScopedJavaUnicodeString nan(env, nan0); UnicodeString groupingSeparator(groupingSeparator0); DecimalFormatSymbols* result = new DecimalFormatSymbols; result->setSymbol(DecimalFormatSymbols::kCurrencySymbol, currencySymbol.unicodeString()); result->setSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol, UnicodeString(decimalSeparator)); result->setSymbol(DecimalFormatSymbols::kDigitSymbol, UnicodeString(digit)); result->setSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol, groupingSeparator); result->setSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol, groupingSeparator); result->setSymbol(DecimalFormatSymbols::kInfinitySymbol, infinity.unicodeString()); result->setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, internationalCurrencySymbol.unicodeString()); result->setSymbol(DecimalFormatSymbols::kMinusSignSymbol, UnicodeString(minusSign)); result->setSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol, UnicodeString(monetaryDecimalSeparator)); result->setSymbol(DecimalFormatSymbols::kNaNSymbol, nan.unicodeString()); result->setSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol, UnicodeString(patternSeparator)); result->setSymbol(DecimalFormatSymbols::kPercentSymbol, UnicodeString(percent)); result->setSymbol(DecimalFormatSymbols::kPerMillSymbol, UnicodeString(perMill)); result->setSymbol(DecimalFormatSymbols::kZeroDigitSymbol, UnicodeString(zeroDigit)); return result; } static void setDecimalFormatSymbols(JNIEnv* env, jclass, jint addr, jstring currencySymbol, jchar decimalSeparator, jchar digit, jchar groupingSeparator, jstring infinity, jstring internationalCurrencySymbol, jchar minusSign, jchar monetaryDecimalSeparator, jstring nan, jchar patternSeparator, jchar percent, jchar perMill, jchar zeroDigit) { DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env, currencySymbol, decimalSeparator, digit, groupingSeparator, infinity, internationalCurrencySymbol, minusSign, monetaryDecimalSeparator, nan, patternSeparator, percent, perMill, zeroDigit); toDecimalFormat(addr)->adoptDecimalFormatSymbols(symbols); } static jint openDecimalFormatImpl(JNIEnv* env, jclass clazz, jstring pattern0, jstring currencySymbol, jchar decimalSeparator, jchar digit, jchar groupingSeparator, jstring infinity, jstring internationalCurrencySymbol, jchar minusSign, jchar monetaryDecimalSeparator, jstring nan, jchar patternSeparator, jchar percent, jchar perMill, jchar zeroDigit) { if (pattern0 == NULL) { jniThrowNullPointerException(env, NULL); return 0; } UErrorCode status = U_ZERO_ERROR; UParseError parseError; ScopedJavaUnicodeString pattern(env, pattern0); DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env, currencySymbol, decimalSeparator, digit, groupingSeparator, infinity, internationalCurrencySymbol, minusSign, monetaryDecimalSeparator, nan, patternSeparator, percent, perMill, zeroDigit); DecimalFormat* fmt = new DecimalFormat(pattern.unicodeString(), symbols, parseError, status); if (fmt == NULL) { delete symbols; } icu4jni_error(env, status); return static_cast(reinterpret_cast(fmt)); } static void closeDecimalFormatImpl(JNIEnv *env, jclass clazz, jint addr) { delete toDecimalFormat(addr); } static void setSymbol(JNIEnv* env, jclass, jint addr, jint symbol, jstring s) { const UChar* chars = env->GetStringChars(s, NULL); const int32_t charCount = env->GetStringLength(s); UErrorCode status = U_ZERO_ERROR; UNumberFormat* fmt = reinterpret_cast(static_cast(addr)); unum_setSymbol(fmt, static_cast(symbol), chars, charCount, &status); icu4jni_error(env, status); env->ReleaseStringChars(s, chars); } static void setAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol, jint value) { UNumberFormat *fmt = (UNumberFormat *)(int)addr; unum_setAttribute(fmt, (UNumberFormatAttribute) symbol, value); } static jint getAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol) { UNumberFormat *fmt = (UNumberFormat *)(int)addr; int res = unum_getAttribute(fmt, (UNumberFormatAttribute) symbol); return res; } static void setTextAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol, jstring text) { // the errorcode returned by unum_setTextAttribute UErrorCode status = U_ZERO_ERROR; // get the pointer to the number format UNumberFormat *fmt = (UNumberFormat *)(int)addr; const UChar *textChars = env->GetStringChars(text, NULL); int textLen = env->GetStringLength(text); unum_setTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, textChars, textLen, &status); env->ReleaseStringChars(text, textChars); icu4jni_error(env, status); } static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol) { uint32_t resultlength, reslenneeded; // the errorcode returned by unum_getTextAttribute UErrorCode status = U_ZERO_ERROR; // get the pointer to the number format UNumberFormat *fmt = (UNumberFormat *)(int)addr; UChar* result = NULL; resultlength=0; // find out how long the result will be reslenneeded=unum_getTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, result, resultlength, &status); result = NULL; if(status==U_BUFFER_OVERFLOW_ERROR) { status=U_ZERO_ERROR; resultlength=reslenneeded+1; result=(UChar*)malloc(sizeof(UChar) * resultlength); reslenneeded=unum_getTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, result, resultlength, &status); } if (icu4jni_error(env, status) != FALSE) { return NULL; } jstring res = env->NewString(result, reslenneeded); free(result); return res; } static void applyPatternImpl(JNIEnv *env, jclass clazz, jint addr, jboolean localized, jstring pattern0) { if (pattern0 == NULL) { jniThrowNullPointerException(env, NULL); return; } ScopedJavaUnicodeString pattern(env, pattern0); DecimalFormat* fmt = toDecimalFormat(addr); UErrorCode status = U_ZERO_ERROR; if (localized) { fmt->applyLocalizedPattern(pattern.unicodeString(), status); } else { fmt->applyPattern(pattern.unicodeString(), status); } icu4jni_error(env, status); } static jstring toPatternImpl(JNIEnv *env, jclass, jint addr, jboolean localized) { DecimalFormat* fmt = toDecimalFormat(addr); UnicodeString pattern; if (localized) { fmt->toLocalizedPattern(pattern); } else { fmt->toPattern(pattern); } return env->NewString(pattern.getBuffer(), pattern.length()); } template static jstring format(JNIEnv *env, jint addr, jobject field, jstring fieldType, jobject attributes, T val) { UErrorCode status = U_ZERO_ERROR; DecimalFormat::AttributeBuffer attrBuffer; attrBuffer.buffer = NULL; DecimalFormat::AttributeBuffer* attrBufferPtr = NULL; if (attributes != NULL || (fieldType != NULL && field != NULL)) { attrBufferPtr = &attrBuffer; // ICU requires that this is dynamically allocated and non-zero size. // ICU grows it in chunks of 128 bytes, so that's a reasonable initial size. attrBuffer.bufferSize = 128; attrBuffer.buffer = new char[attrBuffer.bufferSize]; attrBuffer.buffer[0] = '\0'; } FieldPosition fp; fp.setField(FieldPosition::DONT_CARE); UnicodeString str; DecimalFormat* fmt = toDecimalFormat(addr); fmt->format(val, str, fp, attrBufferPtr); if (attrBufferPtr && strlen(attrBuffer.buffer) > 0) { // check if we want to get all attributes if (attributes != NULL) { jstring attrString = env->NewStringUTF(attrBuffer.buffer + 1); // cut off the leading ';' jclass stringBufferClass = env->FindClass("java/lang/StringBuffer"); jmethodID appendMethodID = env->GetMethodID(stringBufferClass, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); env->CallObjectMethod(attributes, appendMethodID, attrString); } // check if we want one special attribute returned in the given FieldPos if (fieldType != NULL && field != NULL) { const char* fieldName = env->GetStringUTFChars(fieldType, NULL); const char* delimiter = ";"; char* context = NULL; char* resattr = strtok_r(attrBuffer.buffer, delimiter, &context); while (resattr != NULL && strcmp(resattr, fieldName) != 0) { resattr = strtok_r(NULL, delimiter, &context); } if (resattr != NULL && strcmp(resattr, fieldName) == 0) { resattr = strtok_r(NULL, delimiter, &context); int begin = (int) strtol(resattr, NULL, 10); resattr = strtok_r(NULL, delimiter, &context); int end = (int) strtol(resattr, NULL, 10); jclass fieldPositionClass = env->FindClass("java/text/FieldPosition"); jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass, "setBeginIndex", "(I)V"); jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass, "setEndIndex", "(I)V"); env->CallVoidMethod(field, setBeginIndexMethodID, (jint) begin); env->CallVoidMethod(field, setEndIndexMethodID, (jint) end); } env->ReleaseStringUTFChars(fieldType, fieldName); } } jstring result = env->NewString(str.getBuffer(), str.length()); delete[] attrBuffer.buffer; return result; } static jstring formatLong(JNIEnv* env, jclass, jint addr, jlong value, jobject field, jstring fieldType, jobject attributes) { int64_t longValue = value; return format(env, addr, field, fieldType, attributes, longValue); } static jstring formatDouble(JNIEnv* env, jclass, jint addr, jdouble value, jobject field, jstring fieldType, jobject attributes) { double doubleValue = value; return format(env, addr, field, fieldType, attributes, doubleValue); } static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring value, jobject field, jstring fieldType, jobject attributes, jint scale) { // const char * valueUTF = env->GetStringUTFChars(value, NULL); // LOGI("ENTER formatDigitList: %s, scale: %d", valueUTF, scale); // env->ReleaseStringUTFChars(value, valueUTF); if (scale < 0) { icu4jni_error(env, U_ILLEGAL_ARGUMENT_ERROR); return NULL; } const char * fieldName = NULL; if(fieldType != NULL) { fieldName = env->GetStringUTFChars(fieldType, NULL); } uint32_t reslenneeded; // prepare digit list const char *valueChars = env->GetStringUTFChars(value, NULL); bool isInteger = (scale == 0); bool isPositive = (*valueChars != '-'); // skip the '-' if the number is negative const char *digits = (isPositive ? valueChars : valueChars + 1); int length = strlen(digits); DecimalFormat* fmt = toDecimalFormat(addr); // The length of our digit list buffer must be the actual string length + 3, // because ICU will append some additional characters at the head and at the // tail of the string, in order to keep strtod() happy: // // - The sign "+" or "-" is appended at the head // - The exponent "e" and the "\0" terminator is appended at the tail // // In retrospect, the changes to ICU's DigitList that were necessary for // big numbers look a bit hacky. It would make sense to rework all this // once ICU 4.x has been integrated into Android. Ideally, big number // support would make it into ICU itself, so we don't need our private // fix anymore. DigitList digitList(length + 3); digitList.fCount = length; strcpy(digitList.fDigits, digits); env->ReleaseStringUTFChars(value, valueChars); digitList.fDecimalAt = digitList.fCount - scale; digitList.fIsPositive = isPositive; digitList.fRoundingMode = fmt->getRoundingMode(); digitList.round(fmt->getMaximumFractionDigits() + digitList.fDecimalAt); UChar *result = NULL; FieldPosition fp; fp.setField(FieldPosition::DONT_CARE); fp.setBeginIndex(0); fp.setEndIndex(0); UErrorCode status = U_ZERO_ERROR; DecimalFormat::AttributeBuffer *attrBuffer = NULL; attrBuffer = (DecimalFormat::AttributeBuffer *) calloc(sizeof(DecimalFormat::AttributeBuffer), 1); attrBuffer->bufferSize = 128; attrBuffer->buffer = (char *) calloc(129 * sizeof(char), 1); UnicodeString res; fmt->subformat(res, fp, attrBuffer, digitList, isInteger); reslenneeded = res.extract(NULL, 0, status); if(status==U_BUFFER_OVERFLOW_ERROR) { status=U_ZERO_ERROR; result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1)); res.extract(result, reslenneeded + 1, status); if (icu4jni_error(env, status) != FALSE) { if(fieldType != NULL) { env->ReleaseStringUTFChars(fieldType, fieldName); } free(result); free(attrBuffer->buffer); free(attrBuffer); return NULL; } } else { if(fieldType != NULL) { env->ReleaseStringUTFChars(fieldType, fieldName); } free(attrBuffer->buffer); free(attrBuffer); return NULL; } int attrLength = (strlen(attrBuffer->buffer) + 1 ); if(attrLength > 1) { // check if we want to get all attributes if(attributes != NULL) { // prepare the classes and method ids const char * stringBufferClassName = "java/lang/StringBuffer"; jclass stringBufferClass = env->FindClass(stringBufferClassName); jmethodID appendMethodID = env->GetMethodID(stringBufferClass, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); jstring attrString = env->NewStringUTF(attrBuffer->buffer + 1); // cut off the leading ';' env->CallObjectMethod(attributes, appendMethodID, attrString); } // check if we want one special attribute returned in the given FieldPos if(fieldName != NULL && field != NULL) { const char *delimiter = ";"; int begin; int end; char * resattr; resattr = strtok(attrBuffer->buffer, delimiter); while(resattr != NULL && strcmp(resattr, fieldName) != 0) { resattr = strtok(NULL, delimiter); } if(resattr != NULL && strcmp(resattr, fieldName) == 0) { // prepare the classes and method ids const char * fieldPositionClassName = "java/text/FieldPosition"; jclass fieldPositionClass = env->FindClass( fieldPositionClassName); jmethodID setBeginIndexMethodID = env->GetMethodID( fieldPositionClass, "setBeginIndex", "(I)V"); jmethodID setEndIndexMethodID = env->GetMethodID( fieldPositionClass, "setEndIndex", "(I)V"); resattr = strtok(NULL, delimiter); begin = (int) strtol(resattr, NULL, 10); resattr = strtok(NULL, delimiter); end = (int) strtol(resattr, NULL, 10); env->CallVoidMethod(field, setBeginIndexMethodID, (jint) begin); env->CallVoidMethod(field, setEndIndexMethodID, (jint) end); } } } if(fieldType != NULL) { env->ReleaseStringUTFChars(fieldType, fieldName); } jstring resulting = env->NewString(result, reslenneeded); free(attrBuffer->buffer); free(attrBuffer); free(result); // const char * resultUTF = env->GetStringUTFChars(resulting, NULL); // LOGI("RETURN formatDigitList: %s", resultUTF); // env->ReleaseStringUTFChars(resulting, resultUTF); return resulting; } static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, jobject position) { // TODO: cache these? jclass parsePositionClass = env->FindClass("java/text/ParsePosition"); jclass longClass = env->FindClass("java/lang/Long"); jclass doubleClass = env->FindClass("java/lang/Double"); jclass bigDecimalClass = env->FindClass("java/math/BigDecimal"); jclass bigIntegerClass = env->FindClass("java/math/BigInteger"); jmethodID getIndexMethodID = env->GetMethodID(parsePositionClass, "getIndex", "()I"); jmethodID setIndexMethodID = env->GetMethodID(parsePositionClass, "setIndex", "(I)V"); jmethodID setErrorIndexMethodID = env->GetMethodID(parsePositionClass, "setErrorIndex", "(I)V"); jmethodID longInitMethodID = env->GetMethodID(longClass, "", "(J)V"); jmethodID dblInitMethodID = env->GetMethodID(doubleClass, "", "(D)V"); jmethodID bigDecimalInitMethodID = env->GetMethodID(bigDecimalClass, "", "(Ljava/math/BigInteger;I)V"); jmethodID bigIntegerInitMethodID = env->GetMethodID(bigIntegerClass, "", "(Ljava/lang/String;)V"); jmethodID doubleValueMethodID = env->GetMethodID(bigDecimalClass, "doubleValue", "()D"); // make sure the ParsePosition is valid. Actually icu4c would parse a number // correctly even if the parsePosition is set to -1, but since the RI fails // for that case we have to fail too int parsePos = env->CallIntMethod(position, getIndexMethodID, NULL); const int strlength = env->GetStringLength(text); if(parsePos < 0 || parsePos > strlength) { return NULL; } ParsePosition pp; pp.setIndex(parsePos); DigitList digits; UNumberFormat *fmt = (UNumberFormat *)(int)addr; Formattable res; bool resultAssigned; jchar *str = (UChar *)env->GetStringChars(text, NULL); const UnicodeString src((UChar*)str, strlength, strlength); ((const DecimalFormat*)fmt)->parse(src, resultAssigned, res, pp, FALSE, digits); env->ReleaseStringChars(text, str); if(pp.getErrorIndex() == -1) { parsePos = pp.getIndex(); } else { env->CallVoidMethod(position, setErrorIndexMethodID, (jint) pp.getErrorIndex()); return NULL; } Formattable::Type numType = res.getType(); UErrorCode fmtStatus; double resultDouble; long resultLong; int64_t resultInt64; jstring resultStr; jobject resultObject1, resultObject2; if (resultAssigned) { switch(numType) { case Formattable::kDouble: resultDouble = res.getDouble(); env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos); return env->NewObject(doubleClass, dblInitMethodID, (jdouble) resultDouble); case Formattable::kLong: resultLong = res.getLong(); env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos); return env->NewObject(longClass, longInitMethodID, (jlong) resultLong); case Formattable::kInt64: resultInt64 = res.getInt64(); env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos); return env->NewObject(longClass, longInitMethodID, (jlong) resultInt64); default: return NULL; } } else { int scale = digits.fCount - digits.fDecimalAt; // ATTENTION: Abuse of Implementation Knowlegde! digits.fDigits[digits.fCount] = 0; if (digits.fIsPositive) { resultStr = env->NewStringUTF(digits.fDigits); } else { if (digits.fCount == 0) { env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos); return env->NewObject(doubleClass, dblInitMethodID, (jdouble)-0); } else { // ATTENTION: Abuse of Implementation Knowlegde! *(digits.fDigits - 1) = '-'; resultStr = env->NewStringUTF(digits.fDigits - 1); } } env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos); resultObject1 = env->NewObject(bigIntegerClass, bigIntegerInitMethodID, resultStr); resultObject2 = env->NewObject(bigDecimalClass, bigDecimalInitMethodID, resultObject1, scale); return resultObject2; } } static jint cloneDecimalFormatImpl(JNIEnv *env, jclass, jint addr) { DecimalFormat* fmt = toDecimalFormat(addr); return static_cast(reinterpret_cast(fmt->clone())); } static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ {"applyPatternImpl", "(IZLjava/lang/String;)V", (void*) applyPatternImpl}, {"cloneDecimalFormatImpl", "(I)I", (void*) cloneDecimalFormatImpl}, {"closeDecimalFormatImpl", "(I)V", (void*) closeDecimalFormatImpl}, {"format", "(IDLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;", (void*) formatDouble}, {"format", "(IJLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;", (void*) formatLong}, {"format", "(ILjava/lang/String;Ljava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;I)Ljava/lang/String;", (void*) formatDigitList}, {"getAttribute", "(II)I", (void*) getAttribute}, {"getTextAttribute", "(II)Ljava/lang/String;", (void*) getTextAttribute}, {"openDecimalFormatImpl", "(Ljava/lang/String;Ljava/lang/String;CCCLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)I", (void*) openDecimalFormatImpl}, {"parse", "(ILjava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;", (void*) parse}, {"setAttribute", "(III)V", (void*) setAttribute}, {"setDecimalFormatSymbols", "(ILjava/lang/String;CCCLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)V", (void*) setDecimalFormatSymbols}, {"setSymbol", "(IILjava/lang/String;)V", (void*) setSymbol}, {"setTextAttribute", "(IILjava/lang/String;)V", (void*) setTextAttribute}, {"toPatternImpl", "(IZ)Ljava/lang/String;", (void*) toPatternImpl}, }; int register_com_ibm_icu4jni_text_NativeDecimalFormat(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/ibm/icu4jni/text/NativeDecimalFormat", gMethods, NELEM(gMethods)); }