498 lines
17 KiB
Java
498 lines
17 KiB
Java
/**
|
|
*
|
|
*/
|
|
package com.jacob.com;
|
|
|
|
import java.lang.reflect.Array;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.math.MathContext;
|
|
import java.util.Date;
|
|
|
|
/**
|
|
* A utility class used to convert between Java objects and Variants
|
|
*/
|
|
public final class VariantUtilities {
|
|
private VariantUtilities() {
|
|
// utility class with only static methods don't need constructors
|
|
}
|
|
|
|
/**
|
|
* Populates a variant object from a java object. This method attempts to
|
|
* figure out the appropriate Variant type
|
|
*
|
|
* @param targetVariant
|
|
* @param pValueObject
|
|
* @param fByRef
|
|
*/
|
|
protected static void populateVariant(Variant targetVariant,
|
|
Object pValueObject, boolean fByRef) {
|
|
if (pValueObject == null) {
|
|
targetVariant.putEmpty();
|
|
} else if (pValueObject instanceof Integer) {
|
|
if (fByRef) {
|
|
targetVariant.putIntRef(((Integer) pValueObject).intValue());
|
|
} else {
|
|
targetVariant.putInt(((Integer) pValueObject).intValue());
|
|
}
|
|
} else if (pValueObject instanceof Short) {
|
|
if (fByRef) {
|
|
targetVariant.putShortRef(((Short) pValueObject).shortValue());
|
|
} else {
|
|
targetVariant.putShort(((Short) pValueObject).shortValue());
|
|
}
|
|
} else if (pValueObject instanceof String) {
|
|
if (fByRef) {
|
|
targetVariant.putStringRef((String) pValueObject);
|
|
} else {
|
|
targetVariant.putString((String) pValueObject);
|
|
}
|
|
} else if (pValueObject instanceof Boolean) {
|
|
if (fByRef) {
|
|
targetVariant.putBooleanRef(((Boolean) pValueObject)
|
|
.booleanValue());
|
|
} else {
|
|
targetVariant.putBoolean(((Boolean) pValueObject)
|
|
.booleanValue());
|
|
}
|
|
} else if (pValueObject instanceof Double) {
|
|
if (fByRef) {
|
|
targetVariant.putDoubleRef(((Double) pValueObject)
|
|
.doubleValue());
|
|
} else {
|
|
targetVariant.putDouble(((Double) pValueObject).doubleValue());
|
|
}
|
|
} else if (pValueObject instanceof Float) {
|
|
if (fByRef) {
|
|
targetVariant.putFloatRef(((Float) pValueObject).floatValue());
|
|
} else {
|
|
targetVariant.putFloat(((Float) pValueObject).floatValue());
|
|
}
|
|
} else if (pValueObject instanceof BigDecimal) {
|
|
if (fByRef) {
|
|
targetVariant.putDecimalRef(((BigDecimal) pValueObject));
|
|
} else {
|
|
targetVariant.putDecimal(((BigDecimal) pValueObject));
|
|
}
|
|
} else if (pValueObject instanceof Byte) {
|
|
if (fByRef) {
|
|
targetVariant.putByteRef(((Byte) pValueObject).byteValue());
|
|
} else {
|
|
targetVariant.putByte(((Byte) pValueObject).byteValue());
|
|
}
|
|
} else if (pValueObject instanceof Date) {
|
|
if (fByRef) {
|
|
targetVariant.putDateRef((Date) pValueObject);
|
|
} else {
|
|
targetVariant.putDate((Date) pValueObject);
|
|
}
|
|
} else if (pValueObject instanceof Long) {
|
|
if (fByRef) {
|
|
targetVariant.putLongRef(((Long) pValueObject).longValue());
|
|
} else {
|
|
targetVariant.putLong(((Long) pValueObject).longValue());
|
|
}
|
|
} else if (pValueObject instanceof Currency) {
|
|
if (fByRef) {
|
|
targetVariant.putCurrencyRef(((Currency) pValueObject));
|
|
} else {
|
|
targetVariant.putCurrency(((Currency) pValueObject));
|
|
}
|
|
} else if (pValueObject instanceof SafeArray) {
|
|
if (fByRef) {
|
|
targetVariant.putSafeArrayRef((SafeArray) pValueObject);
|
|
} else {
|
|
targetVariant.putSafeArray((SafeArray) pValueObject);
|
|
}
|
|
} else if (pValueObject instanceof Dispatch) {
|
|
if (fByRef) {
|
|
targetVariant.putDispatchRef((Dispatch) pValueObject);
|
|
} else {
|
|
targetVariant.putDispatch((Dispatch) pValueObject);
|
|
}
|
|
} else if (pValueObject instanceof Variant) {
|
|
// newly added 1.12-pre6 to support VT_VARIANT
|
|
targetVariant.putVariant(pValueObject);
|
|
} else {
|
|
// sourceforge patch 2171967
|
|
// used to rely on coercion but sometimes crashed VM
|
|
throw new NotImplementedException(
|
|
"populateVariant() not implemented for "
|
|
+ pValueObject.getClass());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map arguments based on msdn documentation. This method relies on the
|
|
* variant constructor except for arrays.
|
|
*
|
|
* @param objectToBeMadeIntoVariant
|
|
* @return Variant that represents the object
|
|
*/
|
|
protected static Variant objectToVariant(Object objectToBeMadeIntoVariant) {
|
|
if (objectToBeMadeIntoVariant == null) {
|
|
return new Variant();
|
|
} else if (objectToBeMadeIntoVariant instanceof Variant) {
|
|
// if a variant was passed in then be a slacker and just return it
|
|
return (Variant) objectToBeMadeIntoVariant;
|
|
} else if (objectToBeMadeIntoVariant.getClass().isArray()) {
|
|
// automatically convert arrays using reflection
|
|
// handle it differently based on the type of array
|
|
// added primitive support sourceforge 2762275
|
|
SafeArray sa = null;
|
|
int len1 = Array.getLength(objectToBeMadeIntoVariant);
|
|
Class componentType = objectToBeMadeIntoVariant.getClass()
|
|
.getComponentType();
|
|
|
|
if (componentType.isArray()) { // array of arrays
|
|
int max = 0;
|
|
for (int i = 0; i < len1; i++) {
|
|
Object e1 = Array.get(objectToBeMadeIntoVariant, i);
|
|
int len2 = Array.getLength(e1);
|
|
if (max < len2) {
|
|
max = len2;
|
|
}
|
|
}
|
|
sa = new SafeArray(Variant.VariantVariant, len1, max);
|
|
for (int i = 0; i < len1; i++) {
|
|
Object e1 = Array.get(objectToBeMadeIntoVariant, i);
|
|
for (int j = 0; j < Array.getLength(e1); j++) {
|
|
sa.setVariant(i, j, objectToVariant(Array.get(e1, j)));
|
|
}
|
|
}
|
|
} else if (byte.class.equals(componentType)) {
|
|
byte[] arr = (byte[]) objectToBeMadeIntoVariant;
|
|
sa = new SafeArray(Variant.VariantByte, len1);
|
|
for (int i = 0; i < len1; i++) {
|
|
sa.setByte(i, arr[i]);
|
|
}
|
|
} else if (int.class.equals(componentType)) {
|
|
int[] arr = (int[]) objectToBeMadeIntoVariant;
|
|
sa = new SafeArray(Variant.VariantInt, len1);
|
|
for (int i = 0; i < len1; i++) {
|
|
sa.setInt(i, arr[i]);
|
|
}
|
|
} else if (double.class.equals(componentType)) {
|
|
double[] arr = (double[]) objectToBeMadeIntoVariant;
|
|
sa = new SafeArray(Variant.VariantDouble, len1);
|
|
for (int i = 0; i < len1; i++) {
|
|
sa.setDouble(i, arr[i]);
|
|
}
|
|
} else if (long.class.equals(componentType)) {
|
|
long[] arr = (long[]) objectToBeMadeIntoVariant;
|
|
sa = new SafeArray(Variant.VariantLongInt, len1);
|
|
for (int i = 0; i < len1; i++) {
|
|
sa.setLong(i, arr[i]);
|
|
}
|
|
} else {
|
|
// array of object
|
|
sa = new SafeArray(Variant.VariantVariant, len1);
|
|
for (int i = 0; i < len1; i++) {
|
|
sa.setVariant(i, objectToVariant(Array.get(
|
|
objectToBeMadeIntoVariant, i)));
|
|
}
|
|
}
|
|
Variant returnVariant = new Variant();
|
|
populateVariant(returnVariant, sa, false);
|
|
return returnVariant;
|
|
} else {
|
|
// rely on populateVariant to throw an exception if its an
|
|
// invalid type
|
|
Variant returnVariant = new Variant();
|
|
populateVariant(returnVariant, objectToBeMadeIntoVariant, false);
|
|
return returnVariant;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* converts an array of objects into an array of Variants by repeatedly
|
|
* calling obj2Variant(Object)
|
|
*
|
|
* @param arrayOfObjectsToBeConverted
|
|
* @return Variant[]
|
|
*/
|
|
protected static Variant[] objectsToVariants(
|
|
Object[] arrayOfObjectsToBeConverted) {
|
|
Variant vArg[] = new Variant[arrayOfObjectsToBeConverted.length];
|
|
for (int i = 0; i < arrayOfObjectsToBeConverted.length; i++) {
|
|
vArg[i] = objectToVariant(arrayOfObjectsToBeConverted[i]);
|
|
}
|
|
return vArg;
|
|
}
|
|
|
|
/**
|
|
* Convert a JACOB Variant value to a Java object (type conversions).
|
|
* provided in Sourceforge feature request 959381. A fix was done to handle
|
|
* byRef bug report 1607878.
|
|
* <p>
|
|
* Unlike other toXXX() methods, it does not do a type conversion except for
|
|
* special data types (it shouldn't do any!)
|
|
* <p>
|
|
* Converts Variant.VariantArray types to SafeArrays
|
|
*
|
|
* @return Corresponding Java object of the type matching the Variant type.
|
|
* @throws IllegalStateException
|
|
* if no underlying windows data structure
|
|
* @throws NotImplementedException
|
|
* if unsupported conversion is requested
|
|
* @throws JacobException
|
|
* if the calculated result was a JacobObject usually as a
|
|
* result of error
|
|
*/
|
|
protected static Object variantToObject(Variant sourceData) {
|
|
Object result = null;
|
|
|
|
short type = sourceData.getvt(); // variant type
|
|
|
|
if ((type & Variant.VariantArray) == Variant.VariantArray) { // array
|
|
// returned?
|
|
SafeArray array = null;
|
|
type = (short) (type - Variant.VariantArray);
|
|
// From SF Bug 1840487
|
|
// This did call toSafeArray(false) but that meant
|
|
// this was the only variantToObject() that didn't have its own
|
|
// copy of the data so you would end up with weird run time
|
|
// errors after some GC. So now we just get stupid about it and
|
|
// always make a copy just like toSafeArray() does.
|
|
array = sourceData.toSafeArray();
|
|
result = array;
|
|
} else { // non-array object returned
|
|
switch (type) {
|
|
case Variant.VariantEmpty: // 0
|
|
case Variant.VariantNull: // 1
|
|
break;
|
|
case Variant.VariantShort: // 2
|
|
result = new Short(sourceData.getShort());
|
|
break;
|
|
case Variant.VariantShort | Variant.VariantByref: // 2
|
|
result = new Short(sourceData.getShortRef());
|
|
break;
|
|
case Variant.VariantInt: // 3
|
|
result = new Integer(sourceData.getInt());
|
|
break;
|
|
case Variant.VariantInt | Variant.VariantByref: // 3
|
|
result = new Integer(sourceData.getIntRef());
|
|
break;
|
|
case Variant.VariantFloat: // 4
|
|
result = new Float(sourceData.getFloat());
|
|
break;
|
|
case Variant.VariantFloat | Variant.VariantByref: // 4
|
|
result = new Float(sourceData.getFloatRef());
|
|
break;
|
|
case Variant.VariantDouble: // 5
|
|
result = new Double(sourceData.getDouble());
|
|
break;
|
|
case Variant.VariantDouble | Variant.VariantByref: // 5
|
|
result = new Double(sourceData.getDoubleRef());
|
|
break;
|
|
case Variant.VariantCurrency: // 6
|
|
result = sourceData.getCurrency();
|
|
break;
|
|
case Variant.VariantCurrency | Variant.VariantByref: // 6
|
|
result = sourceData.getCurrencyRef();
|
|
break;
|
|
case Variant.VariantDate: // 7
|
|
result = sourceData.getJavaDate();
|
|
break;
|
|
case Variant.VariantDate | Variant.VariantByref: // 7
|
|
result = sourceData.getJavaDateRef();
|
|
break;
|
|
case Variant.VariantString: // 8
|
|
result = sourceData.getString();
|
|
break;
|
|
case Variant.VariantString | Variant.VariantByref: // 8
|
|
result = sourceData.getStringRef();
|
|
break;
|
|
case Variant.VariantDispatch: // 9
|
|
result = sourceData.getDispatch();
|
|
break;
|
|
case Variant.VariantDispatch | Variant.VariantByref: // 9
|
|
result = sourceData.getDispatchRef(); // Can dispatches even
|
|
// be byRef?
|
|
break;
|
|
case Variant.VariantError: // 10
|
|
result = new NotImplementedException(
|
|
"toJavaObject() Not implemented for VariantError");
|
|
break;
|
|
case Variant.VariantBoolean: // 11
|
|
result = new Boolean(sourceData.getBoolean());
|
|
break;
|
|
case Variant.VariantBoolean | Variant.VariantByref: // 11
|
|
result = new Boolean(sourceData.getBooleanRef());
|
|
break;
|
|
case Variant.VariantVariant: // 12 they are always by ref
|
|
result = new NotImplementedException(
|
|
"toJavaObject() Not implemented for VariantVariant without ByRef");
|
|
break;
|
|
case Variant.VariantVariant | Variant.VariantByref: // 12
|
|
result = sourceData.getVariant();
|
|
break;
|
|
case Variant.VariantObject: // 13
|
|
result = new NotImplementedException(
|
|
"toJavaObject() Not implemented for VariantObject");
|
|
break;
|
|
case Variant.VariantDecimal: // 14
|
|
result = sourceData.getDecimal();
|
|
break;
|
|
case Variant.VariantDecimal | Variant.VariantByref: // 14
|
|
result = sourceData.getDecimalRef();
|
|
break;
|
|
case Variant.VariantByte: // 17
|
|
result = new Byte(sourceData.getByte());
|
|
break;
|
|
case Variant.VariantByte | Variant.VariantByref: // 17
|
|
result = new Byte(sourceData.getByteRef());
|
|
break;
|
|
case Variant.VariantLongInt: // 20
|
|
result = new Long(sourceData.getLong());
|
|
break;
|
|
case Variant.VariantLongInt | Variant.VariantByref: // 20
|
|
result = new Long(sourceData.getLongRef());
|
|
break;
|
|
case Variant.VariantTypeMask: // 4095
|
|
result = new NotImplementedException(
|
|
"toJavaObject() Not implemented for VariantTypeMask");
|
|
break;
|
|
case Variant.VariantArray: // 8192
|
|
result = new NotImplementedException(
|
|
"toJavaObject() Not implemented for VariantArray");
|
|
break;
|
|
case Variant.VariantByref: // 16384
|
|
result = new NotImplementedException(
|
|
"toJavaObject() Not implemented for VariantByref");
|
|
break;
|
|
default:
|
|
result = new NotImplementedException("Unknown return type: "
|
|
+ type);
|
|
// there was a "return result" here that caused defect 1602118
|
|
// so it was removed
|
|
break;
|
|
}// switch (type)
|
|
|
|
if (result instanceof JacobException) {
|
|
throw (JacobException) result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}// toJava()
|
|
|
|
/**
|
|
* Verifies that we have a scale 0 <= x <= 28 and now more than 96 bits of
|
|
* data. The roundToMSDecimal method will attempt to adjust a BigDecimal to
|
|
* pass this set of tests
|
|
*
|
|
* @param in
|
|
* @throws IllegalArgumentException
|
|
* if out of bounds
|
|
*/
|
|
protected static void validateDecimalScaleAndBits(BigDecimal in) {
|
|
BigInteger allWordBigInt = in.unscaledValue();
|
|
if (in.scale() > 28) {
|
|
// should this cast to a string and call putStringRef()?
|
|
throw new IllegalArgumentException(
|
|
"VT_DECIMAL only supports a maximum scale of 28 and the passed"
|
|
+ " in value has a scale of " + in.scale());
|
|
} else if (in.scale() < 0) {
|
|
// should this cast to a string and call putStringRef()?
|
|
throw new IllegalArgumentException(
|
|
"VT_DECIMAL only supports a minimum scale of 0 and the passed"
|
|
+ " in value has a scale of " + in.scale());
|
|
} else if (allWordBigInt.bitLength() > 12 * 8) {
|
|
throw new IllegalArgumentException(
|
|
"VT_DECIMAL supports a maximum of "
|
|
+ 12
|
|
* 8
|
|
+ " bits not counting scale and the number passed in has "
|
|
+ allWordBigInt.bitLength());
|
|
|
|
} else {
|
|
// no bounds problem to be handled
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Largest possible number with scale set to 0
|
|
*/
|
|
private static final BigDecimal LARGEST_DECIMAL = new BigDecimal(
|
|
new BigInteger("ffffffffffffffffffffffff", 16));
|
|
/**
|
|
* Smallest possible number with scale set to 0. MS doesn't support negative
|
|
* scales like BigDecimal.
|
|
*/
|
|
private static final BigDecimal SMALLEST_DECIMAL = new BigDecimal(
|
|
new BigInteger("ffffffffffffffffffffffff", 16).negate());
|
|
|
|
/**
|
|
* Does any validation that couldn't have been fixed by rounding or scale
|
|
* modification.
|
|
*
|
|
* @param in
|
|
* The BigDecimal to be validated
|
|
* @throws IllegalArgumentException
|
|
* if the number is too large or too small or null
|
|
*/
|
|
protected static void validateDecimalMinMax(BigDecimal in) {
|
|
if (in == null) {
|
|
throw new IllegalArgumentException(
|
|
"null is not a supported Decimal value.");
|
|
} else if (LARGEST_DECIMAL.compareTo(in) < 0) {
|
|
throw new IllegalArgumentException(
|
|
"Value too large for VT_DECIMAL data type:" + in.toString()
|
|
+ " integer: " + in.toBigInteger().toString(16)
|
|
+ " scale: " + in.scale());
|
|
} else if (SMALLEST_DECIMAL.compareTo(in) > 0) {
|
|
throw new IllegalArgumentException(
|
|
"Value too small for VT_DECIMAL data type:" + in.toString()
|
|
+ " integer: " + in.toBigInteger().toString(16)
|
|
+ " scale: " + in.scale());
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Rounds the scale and bit length so that it will pass
|
|
* validateDecimalScaleBits(). Developers should call this method if they
|
|
* really want MS Decimal and don't want to lose precision.
|
|
* <p>
|
|
* Changing the scale on a number that can fit in an MS Decimal can change
|
|
* the number's representation enough that it will round to a number too
|
|
* large to be represented by an MS VT_DECIMAL
|
|
*
|
|
* @param sourceDecimal
|
|
* @return BigDecimal a new big decimal that was rounded to fit in an MS
|
|
* VT_DECIMAL
|
|
*/
|
|
public static BigDecimal roundToMSDecimal(BigDecimal sourceDecimal) {
|
|
BigInteger sourceDecimalIntComponent = sourceDecimal.unscaledValue();
|
|
BigDecimal destinationDecimal = new BigDecimal(
|
|
sourceDecimalIntComponent, sourceDecimal.scale());
|
|
int roundingModel = BigDecimal.ROUND_HALF_UP;
|
|
validateDecimalMinMax(destinationDecimal);
|
|
// First limit the number of digits and then the precision.
|
|
// Try and round to 29 digits because we can sometimes do that
|
|
BigInteger allWordBigInt;
|
|
allWordBigInt = destinationDecimal.unscaledValue();
|
|
if (allWordBigInt.bitLength() > 96) {
|
|
destinationDecimal = destinationDecimal.round(new MathContext(29));
|
|
// see if 29 digits uses more than 96 bits
|
|
if (allWordBigInt.bitLength() > 96) {
|
|
// Dang. It was over 97 bits so shorten it one more digit to
|
|
// stay <= 96 bits
|
|
destinationDecimal = destinationDecimal.round(new MathContext(
|
|
28));
|
|
}
|
|
}
|
|
// the bit manipulations above may change the scale so do it afterwards
|
|
// round the scale to the max MS can support
|
|
if (destinationDecimal.scale() > 28) {
|
|
destinationDecimal = destinationDecimal.setScale(28, roundingModel);
|
|
}
|
|
if (destinationDecimal.scale() < 0) {
|
|
destinationDecimal = destinationDecimal.setScale(0, roundingModel);
|
|
}
|
|
return destinationDecimal;
|
|
}
|
|
}
|