diff --git a/docs/ReleaseNotes.html b/docs/ReleaseNotes.html index b8fbba9..2d5c25d 100644 --- a/docs/ReleaseNotes.html +++ b/docs/ReleaseNotes.html @@ -28,6 +28,10 @@ 1775889 (M4) Fixed leak setString(int[],value) and other setString() methods + + 1772783 + (M4) Added VT_DECIMAL support for BigDecimals whose scale less than 28 +     diff --git a/jni/Variant.cpp b/jni/Variant.cpp index a63c79c..80af4f1 100644 --- a/jni/Variant.cpp +++ b/jni/Variant.cpp @@ -1062,8 +1062,156 @@ JNIEXPORT jint JNICALL Java_com_jacob_com_Variant_getVariantVariant } return NULL; - } + /** + * puts a VT_DECIMAL by reference + * Added 1.13M4 + * */ + JNIEXPORT void JNICALL Java_com_jacob_com_Variant_putVariantDecRef + (JNIEnv *env, jobject _this, jint signum, jint scale, jint lo, jint mid, jint hi) + { + VARIANT *v = extractVariant(env, _this); + if (v) { + VariantClear(v); // whatever was there before + DECIMAL *pd = (DECIMAL *)CoTaskMemAlloc(sizeof(DECIMAL)); + pd->scale = scale; + pd->sign = signum == 1?0:0x80; + pd->Hi32 = hi; + pd->Mid32 = mid; + pd->Lo32 = lo; + V_VT(v) = VT_DECIMAL | VT_BYREF; + V_DECIMALREF(v) = pd; + } + } -} \ No newline at end of file + + /** + * puts a VT_DECIMAL + * Added 1.13M4 + * */ + JNIEXPORT void JNICALL Java_com_jacob_com_Variant_putVariantDec + (JNIEnv *env, jobject _this, jint signum, jint scale, jint lo, jint mid, jint hi) + { + VARIANT *v = extractVariant(env, _this); + DECIMAL *d; + if (v) { + VariantClear(v); // whatever was there before + d = (DECIMAL*)v; + d->scale = scale; + d->sign = signum == 1?0:0x80; + d->Hi32 = hi; + d->Mid32 = mid; + d->Lo32 = lo; + V_VT(v) = VT_DECIMAL; + } + } + +/** + * utility method used by the getVariantXXX() methods to convert VT_DECIMAL to BigDecimal + * */ +jobject extractDecimal + (JNIEnv *env, DECIMAL* d) + { + jclass bigIntegerClass; + jclass bigDecimalClass; + jobject integer; + jmethodID bigIntegerConstructor; + jmethodID bigDecimalConstructor; + jbyteArray bArray; + jobject result = NULL; + jbyte* buffer; + + bigIntegerClass = env->FindClass("java/math/BigInteger"); + if (bigIntegerClass == NULL) + return NULL; + bigDecimalClass = env->FindClass("java/math/BigDecimal"); + if (bigDecimalClass == NULL) { + env->DeleteLocalRef(bigIntegerClass); + return NULL; + } + + bigIntegerConstructor = env->GetMethodID(bigIntegerClass, "", "(I[B)V"); + if (bigIntegerConstructor == NULL) { + env->DeleteLocalRef(bigIntegerClass); + env->DeleteLocalRef(bigDecimalClass); + return NULL; + } + bigDecimalConstructor = env->GetMethodID(bigDecimalClass, "", "(Ljava/math/BigInteger;I)V"); + if (bigIntegerConstructor == NULL) { + env->DeleteLocalRef(bigIntegerClass); + env->DeleteLocalRef(bigDecimalClass); + return NULL; + } + bArray = env->NewByteArray(12); + if (bArray == NULL) { + env->DeleteLocalRef(bigIntegerClass); + env->DeleteLocalRef(bigDecimalClass); + return NULL; + } + /* Unfortunately the byte ordering is completely wrong, so we remap it into buffer */ + buffer = (jbyte*)malloc(12); + buffer[11] = d->Lo32 & 255; + buffer[10] = (d->Lo32 >> 8) & 255; + buffer[9] = (d->Lo32 >> 16) & 255;; + buffer[8] = (d->Lo32 >> 24) & 255;; + buffer[7] = (d->Mid32) & 255;; + buffer[6] = (d->Mid32 >> 8) & 255; + buffer[5] = (d->Mid32 >> 16) & 255; + buffer[4] = (d->Mid32 >> 24) & 255; + buffer[3] = (d->Hi32) & 255; + buffer[2] = (d->Hi32 >> 8) & 255; + buffer[1] = (d->Hi32 >> 16) & 255; + buffer[0] = (d->Hi32 >> 24) & 255; + /* Load buffer into the actual array */ + env->SetByteArrayRegion(bArray, 0, 12, buffer); + /* then clean up the C array */ + free(buffer); + + /* instantiate the BigInteger */ + integer = env->NewObject(bigIntegerClass, bigIntegerConstructor, d->sign == 0x80?-1:1, bArray); + + result = env->NewObject(bigDecimalClass, bigDecimalConstructor, integer, (jint)(d->scale)); + + /* Clean up the Java global references */ + env->DeleteLocalRef(bArray); + env->DeleteLocalRef(integer); + env->DeleteLocalRef(bigIntegerClass); + return result; + } + +/** + * gets a VT_DECIMAL by ref as a BigDecimal + * Added 1.13M4 + * */ +JNIEXPORT jobject JNICALL Java_com_jacob_com_Variant_getVariantDecRef + (JNIEnv *env, jobject _this) + { + VARIANT *v = extractVariant(env, _this); + if (v) { + if (V_VT(v) != (VT_DECIMAL|VT_BYREF)) { + return NULL; + } + return extractDecimal(env, v->pdecVal); + } + return NULL; + } + +/** + * gets a VT_DECIMAL as a BigDecimal + * Added 1.13M4 + * */ +JNIEXPORT jobject JNICALL Java_com_jacob_com_Variant_getVariantDec + (JNIEnv *env, jobject _this) + { + VARIANT *v = extractVariant(env, _this); + if (v) { + if (V_VT(v) != VT_DECIMAL) { + return NULL; + } + return extractDecimal(env, (DECIMAL*)v); + } + return NULL; + } + +} diff --git a/jni/Variant.h b/jni/Variant.h index cd68885..a70e3b3 100644 --- a/jni/Variant.h +++ b/jni/Variant.h @@ -513,6 +513,40 @@ JNIEXPORT void JNICALL Java_com_jacob_com_Variant_putVariantVariant JNIEXPORT jint JNICALL Java_com_jacob_com_Variant_getVariantVariant (JNIEnv *, jobject); +/* + * Class: com_jacob_com_Variant + * Method: putVariantDecRef + * Signature: (Ljava.math.BigDecimal;)V + */ +JNIEXPORT void JNICALL Java_com_jacob_com_Variant_putVariantDecRef + (JNIEnv *env, jobject _this, jint signum, jint scale, jint lo, jint mid, jint hi); + + +/* + * Class: com_jacob_com_Variant + * Method: putVariantDec + * Signature: (Ljava.math.BigDecimal;)V + */ +JNIEXPORT void JNICALL Java_com_jacob_com_Variant_putVariantDec + (JNIEnv *env, jobject _this, jint signum, jint scale, jint lo, jint mid, jint hi); + + +/* + * Class: com_jacob_com_Variant + * Method: getVariantDecRef + * Signature: ()Ljava.math.BigDecimal + */ +JNIEXPORT jobject JNICALL Java_com_jacob_com_Variant_getVariantDecRef + (JNIEnv *, jobject); + +/* + * Class: com_jacob_com_Variant + * Method: getVariantDec + * Signature: ()Ljava.math.BigDecimal + */ +JNIEXPORT jobject JNICALL Java_com_jacob_com_Variant_getVariantDec + (JNIEnv *, jobject); + /* * Class: com_jacob_com_Variant * Method: isVariantConsideredNull diff --git a/src/com/jacob/com/Variant.java b/src/com/jacob/com/Variant.java index f11d97f..b2e8afb 100644 --- a/src/com/jacob/com/Variant.java +++ b/src/com/jacob/com/Variant.java @@ -20,6 +20,8 @@ package com.jacob.com; import java.util.Date; +import java.math.BigDecimal; +import java.math.BigInteger; /** * The multi-format data type used for all call backs and most communications @@ -118,6 +120,9 @@ public class Variant extends JacobObject { /** variant's type is object VT_UNKNOWN*/ public static final short VariantObject = 13; + /** variant's type is object VT_DECIMAL*/ + public static final short VariantDecimal = 14; + /** variant's type is byte VT_UI1 */ public static final short VariantByte = 17; @@ -295,6 +300,40 @@ public class Variant extends JacobObject { putVariantIntRef(in); } + /** + * private JNI method called by putDecimalRef + * @param signum sign + * @param scale BigDecimal's scale + * @param lo low 32 bits + * @param mid middle 32 bits + * @param hi high 32 bits + */ + private native void putVariantDecRef(int signum, int scale, int lo, int mid, int hi); + + /** + * Set the content of this variant to an int (VT_DECIMAL|VT_BYREF) + * This may throw exceptions more often than the caller expects because + * most callers don't manage the scale of their BigDecimal objects. + * @param in the BigDecimal that will be converted to VT_DECIMAL + * @throws IllegalArgumentException if the scale is > 28, the maximum for VT_DECIMAL + */ + public void putDecimalRef(BigDecimal in){ + // verify we aren't released + getvt(); + int scale = in.scale(); + if (scale > 28) { + // should this really cast to a string and call putStringRef()? + throw new IllegalArgumentException("VT_DECIMAL only supports a scale of 28 and the passed"+ + " in value has a scale of "+scale); + } + else { + BigInteger unscaled = in.unscaledValue(); + BigInteger shifted = unscaled.shiftRight(32); + putVariantDecRef(in.signum(), scale, unscaled.intValue(), shifted.intValue(), shifted.shiftRight(32).intValue()); + } + } + + /** * set the content of this variant to a double (VT_R8|VT_BYREF) * @param in @@ -744,6 +783,81 @@ public class Variant extends JacobObject { putVariantInt(in); } + + /** + * @return the value in this Variant as a decimal, null if not a decimal + */ + private native Object getVariantDec(); + + + /** + * @return the value in this Variant (byref) as a decimal, null if not a decimal + */ + private native Object getVariantDecRef(); + + + /** + * return the BigDecimal value held in this variant (fails on other types) + * @return BigDecimal + * @throws IllegalStateException if variant is not of the requested type + */ + public BigDecimal getDecimal(){ + if (this.getvt() == VariantDecimal){ + return (BigDecimal)(getVariantDec()); + } else { + throw new IllegalStateException( + "getDecimal() only legal on Variants of type VariantDecimal, not "+this.getvt()); + } + } + + /** + * return the BigDecimal value held in this variant (fails on other types) + * @return BigDecimal + * @throws IllegalStateException if variant is not of the requested type + */ + public BigDecimal getDecimalRef(){ + if ((this.getvt() & VariantDecimal) == VariantDecimal && + (this.getvt() & VariantByref) == VariantByref) { + return (BigDecimal)(getVariantDecRef()); + } else { + throw new IllegalStateException( + "getDecimalRef() only legal on byRef Variants of type VariantDecimal, not "+this.getvt()); + } + } + + /** + * private JNI method called by putDecimal + * @param signum sign + * @param scale BigDecimal's scale + * @param lo low 32 bits + * @param mid middle 32 bits + * @param hi high 32 bits + */ + private native void putVariantDec(int signum, int scale, int lo, int mid, int hi); + + /** + * Set the value of this variant and set the type. + * This may throw exceptions more often than the caller expects because + * most callers don't manage the scale of their BigDecimal objects. + * @param in the big decimal that will convert to the VT_DECIMAL type + * @throws IllegalArgumentException if the scale is > 28, the maximum for VT_DECIMAL + */ + public void putDecimal(BigDecimal in){ + // verify we aren't released yet + getvt(); + int scale = in.scale(); + if (scale > 28) { + // should this really cast to a string and call putStringRef()? + throw new IllegalArgumentException("VT_DECIMAL only supports a scale of 28 and the passed"+ + " in value has a scale of "+scale); + } + else { + BigInteger unscaled = in.unscaledValue(); + BigInteger shifted = unscaled.shiftRight(32); + putVariantDec(in.signum(), in.scale(), unscaled.intValue(), shifted.intValue(), shifted.shiftRight(32).intValue()); + } + } + /** * set the value of this variant * @param in @@ -1514,6 +1628,11 @@ public class Variant extends JacobObject { putFloatRef(((Float) pValueObject).floatValue()); else putFloat(((Float) pValueObject).floatValue()); + } else if (pValueObject instanceof BigDecimal) { + if (fByRef) + putDecimalRef(((BigDecimal) pValueObject)); + else + putDecimal(((BigDecimal) pValueObject)); } else if (pValueObject instanceof Byte){ if (fByRef){ putByteRef(((Byte)pValueObject).byteValue()); @@ -1805,6 +1924,12 @@ public class Variant extends JacobObject { case Variant.VariantObject : //13 result = new NotImplementedException("toJavaObject() Not implemented for VariantObject"); break; + case Variant.VariantDecimal : //14 + result = getDecimal(); + break; + case Variant.VariantDecimal | Variant.VariantByref: //14 + result = getDecimalRef(); + break; case Variant.VariantByte : //17 result = new Byte(this.getByte()); break; diff --git a/unittest/com/jacob/com/VariantTest.java b/unittest/com/jacob/com/VariantTest.java index 6af029e..dd2b3df 100644 --- a/unittest/com/jacob/com/VariantTest.java +++ b/unittest/com/jacob/com/VariantTest.java @@ -1,5 +1,6 @@ package com.jacob.com; +import java.math.BigDecimal; import java.util.Date; import com.jacob.test.BaseTestCase; @@ -72,6 +73,17 @@ public class VariantTest extends BaseTestCase { +" java objects come out the same"); } + // Ugh, you have to pick a magic number whose scale is less than 28 + // 53.53 had a scale of 64 and 53.52 had a scale of 47 + BigDecimal testDecimal = new BigDecimal(53.50); + v = new Variant(testDecimal,false); + vByRef = new Variant(testDecimal,true); + if (!v.toJavaObject().equals(vByRef.toJavaObject())){ + fail(v.toString() + " could not make type " + + v.getvt() +" and "+ vByRef.getvt() + +" java objects come out the same"); + } + Date now = new Date(); v = new Variant(now,false); vByRef = new Variant(now,true); @@ -307,42 +319,42 @@ public class VariantTest extends BaseTestCase { */ public void testPutsAndGets(){ Variant v = new Variant(); - v.putInt(10); - if (v.getInt() != 10){ - fail("int test failed"); - } - v.putShort((short)10); - if (v.getShort() != 10){ - fail("short test failed"); - } - v.putByte((byte)10); - if (v.getByte() != 10){ - fail("int test failed"); - } - v.putFloat(10); - if (v.getFloat() != 10.0){ + + v.putInt((int)10); + assertEquals("int test failed", (int)10, v.getInt()); + + v.putShort((short)20); + assertEquals("short test failed", (short)20, v.getShort()); + + v.putByte((byte)30); + assertEquals("byte test failed", (byte)30, v.getByte()); + + v.putFloat(40); + if (v.getFloat() != 40.0){ fail("float test failed"); } - v.putDouble(10); - if (v.getDouble() != 10.0){ + + v.putDouble(50); + if (v.getDouble() != 50.0){ fail("double test failed"); } + v.putString("1234.567"); - if (!"1234.567".equals(v.getString())){ - fail("string test failed"); - } + assertEquals("string test failed","1234.567", v.getString()); + v.putBoolean(true); - if (v.getBoolean() != true){ - fail("failed boolean test(true)"); - } + assertEquals("failed boolean test(true)",true, v.getBoolean()); + v.putBoolean(false); - if (v.getBoolean() != false){ - fail("failed boolean test(false)"); - } + assertEquals("failed boolean test(false)",false,v.getBoolean()); + v.putCurrency(123456789123456789L); - if (v.getCurrency()!=123456789123456789L){ - fail("failed currency test"); - } + assertEquals("failed currency test",123456789123456789L, v.getCurrency()); + + BigDecimal testDecimal = new BigDecimal("22.222"); + v.putDecimal(testDecimal); + assertEquals("failed BigDecimal test", testDecimal,v.getDecimal()); + Date ourDate = new Date(); v.putDate(ourDate);