//--------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
//--------------------------------------------------------------------------
package ariba.util.expr;
import ariba.util.fieldtype.TypeProvider;
import ariba.util.fieldvalue.OrderedList;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import ariba.util.fieldtype.NumericTypes;
import ariba.util.fieldtype.PrimitiveTypeProvider;
import ariba.util.fieldtype.TypeInfo;
import ariba.util.core.ArithmeticOperations;
import ariba.util.core.StringUtil;
/**
* This is an abstract class with static methods that define the operations of AribaExpr.
* @author Luke Blanshard (blanshlu@netscape.net)
* @author Drew Davidson (drew@ognl.org)
*/
public abstract class ExprOps implements NumericTypes
{
private static ConcurrentHashMap<String, String> _customNumericTypes =
new ConcurrentHashMap<String, String>();
/**
Registers the specified type as a non-built-in numeric type. <p>
Notes:<ul>
<li> This type must have an associated ArithmeticOperations class extension
<li> It is assumed that all built-in types are convertible to
this custom type. Thus any binary expression that includes a
custom numeric type, the custom numeric types ArithmeticOperations
interface will be called.
<li> It is assumed that the custom numeric type is *not* convertible
to the built-in types.
</ul>
@aribaapi
*/
public static void registerNumericType (String typeName)
{
_customNumericTypes.put(typeName, typeName);
}
/**
* Compares two objects for equality, even if it has to convert
* one of them to the other type. If both objects are numeric
* they are converted to the widest type and compared. If
* one is non-numeric and one is numeric the non-numeric is
* converted to double and compared to the double numeric
* value. If both are non-numeric and Comparable and the
* types are compatible (i.e. v1 is of the same or superclass
* of v2's type) they are compared with Comparable.compareTo().
* If both values are non-numeric and not Comparable or of
* incompatible classes this will throw and IllegalArgumentException.
*
* @param v1 First value to compare
* @param v2 second value to compare
*
* @return integer describing the comparison between the two objects.
* A negative number indicates that v1 < v2. Positive indicates
* that v1 > v2. Zero indicates v1 == v2.
*
* @throws IllegalArgumentException if the objects are both non-numeric
* yet of incompatible types or do not implement Comparable.
*/
public static int compareWithConversion (Object v1, Object v2)
{
return compareWithConversion(v1, v2, false);
}
/**
* Compares two objects for equality, even if it has to convert
* one of them to the other type. If both objects are numeric
* they are converted to the widest type and compared. If
* one is non-numeric and one is numeric the non-numeric is
* converted to double and compared to the double numeric
* value. If both are non-numeric and Comparable and the
* types are compatible (i.e. v1 is of the same or superclass
* of v2's type) they are compared with Comparable.compareTo().
* If both values are non-numeric and not Comparable or of
* incompatible classes this will throw and IllegalArgumentException.
*
* @param v1 First value to compare
* @param v2 second value to compare
*
* @return integer describing the comparison between the two objects.
* A negative number indicates that v1 < v2. Positive indicates
* that v1 > v2. Zero indicates v1 == v2.
*
* @throws IllegalArgumentException if the objects are both non-numeric
* yet of incompatible types or do not implement Comparable.
*/
public static int compareWithConversion (Object v1, Object v2, boolean equals)
{
int result;
if (v1 == v2) {
result = 0;
}
else {
int t1 = getNumericType(v1),
t2 = getNumericType(v2),
type = getNumericType(t1, t2, true);
switch (type) {
case BIGINT:
result = bigIntValue(v1).compareTo(bigIntValue(v2));
break;
case BIGDEC:
result = bigDecValue(v1).compareTo(bigDecValue(v2));
break;
case CUSTOMNUMERICTYPE:
ArithmeticOperations operations = (t1 > t2) ?
getArithmeticOperations(v1)
: getArithmeticOperations(v2);
result = operations.compare(v1, v2);
break;
case NONNUMERIC:
if ((t1 == NONNUMERIC || v1 == null) &&
(t2 == NONNUMERIC || v2 == null)) {
if ((v1 == null) || (v2 == null)) {
result = (v1 == v2) ? 0 : (v1 == null ? -1 : 1);
break;
}
else {
if (v1.getClass().isAssignableFrom(v2.getClass()) ||
v2.getClass().isAssignableFrom(v1.getClass())) {
if (v1 instanceof Comparable) {
result = ((Comparable)v1).compareTo(v2);
break;
}
else {
if (equals) {
result = v1.equals(v2) ? 0 : 1;
break;
}
}
}
if (equals) {
// Equals comparison between non-numerics that are not of a common
// superclass return not equal
result = 1;
break;
}
else {
throw new IllegalArgumentException(
"invalid comparison: " + v1.getClass().getName() +
" and " + v2.getClass().getName());
}
}
}
// else fall through
case FLOAT:
case DOUBLE:
try {
double dv1 = doubleValue(v1),
dv2 = doubleValue(v2);
return (dv1 == dv2) ? 0 : ((dv1 < dv2) ? -1 : 1);
}
catch (NumberFormatException e) {
// handle the "fall-through" case where one of the
// operands is non-numeric.
if (equals) {
return v1.equals(v2) ? 0 : 1;
}
throw e;
}
default:
long lv1 = longValue(v1),
lv2 = longValue(v2);
return (lv1 == lv2) ? 0 : ((lv1 < lv2) ? -1 : 1);
}
}
return result;
}
/**
* Returns true if object1 is equal to object2 in either the
* sense that they are the same object or, if both are non-null
* if they are equal in the <CODE>equals()</CODE> sense.
*
* @param object1 First object to compare
* @param object2 Second object to compare
*
* @return true if v1 == v2
*/
public static boolean isEqual (Object object1, Object object2)
{
boolean result = false;
if (object1 == object2) {
result = true;
}
else {
if ((object1 != null) && (object2 != null)) {
if (object1.getClass().isArray() && object2.getClass().isArray() &&
(object2.getClass() == object1.getClass())) {
result = (Array.getLength(object1) == Array.getLength(object2));
if (result) {
for (int i = 0, icount = Array.getLength(object1); result &&
(i < icount); i++) {
result = isEqual(Array.get(object1, i),
Array.get(object2, i));
}
}
}
else {
if ((object1 != null) && (object2 != null)) {
// Check for converted equivalence first, then equals() equivalence
result = (compareWithConversion(object1, object2, true) == 0) ||
object1.equals(object2);
}
}
}
}
return result;
}
/**
* Evaluates the given object as a boolean: if it is a Boolean object, it's easy; if
* it's a Number or a Character, returns true for non-zero objects; and otherwise
* returns true for non-null objects.
*
* @param value an object to interpret as a boolean
* @return the boolean value implied by the given object
*/
public static boolean booleanValue (Object value)
{
if (value == null)
return false;
Class c = value.getClass();
if (c == Boolean.class)
return (Boolean)value;
// if ( c == String.class )
// return ((String)value).length() > 0;
if (c == Character.class)
return (Character)value != 0;
if (value instanceof Number)
return ((Number)value).doubleValue() != 0;
// non-null
return true;
}
/**
* Evaluates the given object as a long integer.
*
* @param value an object to interpret as a long integer
* @return the long integer value implied by the given object
* @throws NumberFormatException if the given object can't be understood as a long integer
*/
public static long longValue (Object value) throws NumberFormatException
{
if (value == null)
return 0L;
Class c = value.getClass();
if ( c.getSuperclass() == Number.class )
return ((Number)value).longValue();
if ( c == Boolean.class )
return (Boolean)value? 1 : 0;
if ( c == Character.class )
return (Character)value;
return Long.parseLong(stringValue(value, true));
}
/**
* Evaluates the given object as a double-precision floating-point number.
*
* @param value an object to interpret as a double
* @return the double value implied by the given object
* @throws NumberFormatException if the given object can't be understood as a double
*/
public static double doubleValue (Object value) throws NumberFormatException
{
if (value == null)
return 0.0;
Class c = value.getClass();
if (c.getSuperclass() == Number.class)
return ((Number)value).doubleValue();
if (c == Boolean.class)
return (Boolean)value? 1 : 0;
if (c == Character.class)
return (Character)value;
String s = stringValue(value, true);
return (s.length() == 0) ? 0.0 : Double.parseDouble(s);
/*
For 1.1 parseDouble() is not available
*/
//return Double.valueOf( value.toString() ).doubleValue();
}
/**
* Evaluates the given object as a BigInteger.
*
* @param value an object to interpret as a BigInteger
* @return the BigInteger value implied by the given object
* @throws NumberFormatException if the given object can't be understood as a BigInteger
*/
public static BigInteger bigIntValue (Object value) throws NumberFormatException
{
if (value == null)
return BigInteger.valueOf(0L);
Class c = value.getClass();
if (c == BigInteger.class)
return (BigInteger)value;
if (c == BigDecimal.class)
return ((BigDecimal)value).toBigInteger();
if (c.getSuperclass() == Number.class)
return BigInteger.valueOf(((Number)value).longValue());
if (c == Boolean.class)
return BigInteger.valueOf((Boolean)value? 1 : 0 );
if (c == Character.class)
return BigInteger.valueOf((Character)value);
return new BigInteger(stringValue(value, true));
}
/**
* Evaluates the given object as a BigDecimal.
*
* @param value an object to interpret as a BigDecimal
* @return the BigDecimal value implied by the given object
* @throws NumberFormatException if the given object can't be understood as a BigDecimal
*/
public static BigDecimal bigDecValue (Object value) throws NumberFormatException
{
if (value == null)
return BigDecimal.valueOf(0L);
Class c = value.getClass();
if (c == BigDecimal.class)
return (BigDecimal)value;
if (c == BigInteger.class)
return new BigDecimal((BigInteger)value);
if (c.getSuperclass() == Number.class )
return new BigDecimal(((Number)value).doubleValue());
if (c == Boolean.class)
return BigDecimal.valueOf((Boolean)value? 1 : 0);
if (c == Character.class)
return BigDecimal.valueOf(((Character)value).charValue());
return new BigDecimal(stringValue(value, true));
}
/**
* Evaluates the given object as a String and trims it if the trim flag is true.
*
* @param value an object to interpret as a String
* @return the String value implied by the given object as returned by the toString() method,
or "null" if the object is null.
*/
public static String stringValue (Object value, boolean trim)
{
String result;
if (value == null) {
result = ExprRuntime.NULL_VALUE_STRING;
}
else {
result = value.toString();
if (trim) {
result = result.trim();
}
}
return result;
}
/**
* Evaluates the given object as a String.
*
* @param value an object to interpret as a String
* @return the String value implied by the given object as returned by the toString() method,
or "null" if the object is null.
*/
public static String stringValue (Object value)
{
return stringValue(value, false);
}
/**
* Returns a constant from the NumericTypes interface that represents the numeric
* type of the given object.
*
* @param value an object that needs to be interpreted as a number
* @return the appropriate constant from the NumericTypes interface
*/
public static int getNumericType (Object value)
{
return getNumericTypeForName(value == null ? null :
value.getClass().getName());
}
public static int getNumericTypeForName (String typeName)
{
TypeProvider tp = PrimitiveTypeProvider.instance();
TypeInfo ti = PrimitiveTypeProvider.getBoxedTypeInfo(tp.getTypeInfo(typeName));
if (ti != null) {
typeName = ti.getName();
}
if (typeName != null) {
if (typeName.equals(Integer.class.getName())) {
return INT;
}
if (typeName.equals(Double.class.getName())) {
return DOUBLE;
}
if (typeName.equals(Boolean.class.getName())) {
return BOOL;
}
if (typeName.equals(Byte.class.getName())) {
return BYTE;
}
if (typeName.equals(Character.class.getName())) {
return CHAR;
}
if (typeName.equals(Short.class.getName())) {
return SHORT;
}
if (typeName.equals(Long.class.getName())) {
return LONG;
}
if (typeName.equals(Float.class.getName())) {
return FLOAT;
}
if (typeName.equals(BigInteger.class.getName())) {
return BIGINT;
}
if (typeName.equals(BigDecimal.class.getName())) {
return BIGDEC;
}
if (_customNumericTypes.containsKey(typeName)) {
return CUSTOMNUMERICTYPE;
}
return NONNUMERIC;
}
return NULL;
}
/**
* Returns the value converted numerically to the given class type
*
* This method also detects when arrays are being converted and
* converts the components of one array to the type of the other.
*
* @param value an object to be converted to the given type
* @param toType class type to be converted to
* @return converted value of the type given, or value if the value
* cannot be converted to the given type.
*/
public static Object convertValue (Object value, Class toType)
{
Object result = null;
if (value != null) {
/* If array -> array then convert components of array individually */
if ( value.getClass().isArray() && toType.isArray() ) {
Class componentType = toType.getComponentType();
result = Array.newInstance(componentType, Array.getLength(value));
for (int i = 0, icount = Array.getLength(value); i < icount; i++) {
Array.set(result, i,
convertValue(Array.get(value, i), componentType));
}
}
else {
if ((toType == Integer.class) || (toType == Integer.TYPE )) {
result = new Integer((int)longValue(value));
}
if ((toType == Double.class) || (toType == Double.TYPE)) {
result = new Double(doubleValue(value));
}
if ((toType == Boolean.class) || (toType == Boolean.TYPE)) {
result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE;
}
if ((toType == Byte.class) || (toType == Byte.TYPE)) {
result = new Byte((byte)longValue(value));
}
if ((toType == Character.class) || (toType == Character.TYPE)) {
result = new Character((char)longValue(value));
}
if ((toType == Short.class) || (toType == Short.TYPE)) {
result = new Short((short)longValue(value));
}
if ((toType == Long.class) || (toType == Long.TYPE)) {
result = new Long(longValue(value));
}
if ((toType == Float.class) || (toType == Float.TYPE)) {
result = new Float(doubleValue(value));
}
if (toType == BigInteger.class) {
result = bigIntValue(value);
}
if (toType == BigDecimal.class) {
result = bigDecValue(value);
}
if (toType == String.class) {
result = stringValue(value);
}
}
}
else {
if (toType.isPrimitive()) {
result = ExprRuntime.getPrimitiveDefaultValue(toType);
}
}
return result;
}
/**
* Returns the constant from the NumericTypes interface that best expresses the type
* of a numeric operation on the two given objects.
*
* @param v1 one argument to a numeric operator
* @param v2 the other argument
* @return the appropriate constant from the NumericTypes interface
*/
public static int getNumericType (Object v1, Object v2)
{
return getNumericType( v1, v2, false );
}
/**
* Returns the constant from the NumericTypes interface that best expresses the type
* of an operation, which can be either numeric or not, on the two given types.
*
* @param t1 type of one argument to an operator
* @param t2 type of the other argument
* @param canBeNonNumeric whether the operator can be interpreted as non-numeric
* @return the appropriate constant from the NumericTypes interface
*/
public static int getNumericType (int t1, int t2, boolean canBeNonNumeric)
{
if ( t1 == t2 && t1 != NULL)
return t1;
if (canBeNonNumeric && (t1 == NONNUMERIC || t2 == NONNUMERIC ||
t1 == CHAR || t2 == CHAR))
return NONNUMERIC;
if (t1 == NONNUMERIC) {
// Try to interpret strings as doubles...
t1 = DOUBLE;
}
if (t2 == NONNUMERIC) {
// Try to interpret strings as doubles...
t2 = DOUBLE;
}
// handling null. If both operands are null, return NONNUMERIC.
// If one of the operand is null, then return the type of the other
// operand.
if (t1 == NULL) {
return (t2 != NULL ? t2 : NONNUMERIC);
}
if (t2 == NULL) {
return t1;
}
if ( t1 >= MIN_REAL_TYPE )
{
if ( t2 >= MIN_REAL_TYPE )
return Math.max(t1,t2);
if ( t2 < INT )
return t1;
if ( t2 == BIGINT )
return BIGDEC;
return Math.max(DOUBLE,t1);
}
else if ( t2 >= MIN_REAL_TYPE )
{
if ( t1 < INT )
return t2;
if ( t1 == BIGINT )
return BIGDEC;
return Math.max(DOUBLE,t2);
}
else {
return Math.max(t1,t2);
}
}
/**
* Returns the constant from the NumericTypes interface that best expresses the type
* of an operation, which can be either numeric or not, on the two given objects.
*
* @param v1 one argument to an operator
* @param v2 the other argument
* @param canBeNonNumeric whether the operator can be interpreted as non-numeric
* @return the appropriate constant from the NumericTypes interface
*/
public static int getNumericType (Object v1, Object v2, boolean canBeNonNumeric)
{
return getNumericType(getNumericType(v1), getNumericType(v2), canBeNonNumeric);
}
public static int getNumericType (
Object v1,
Object v2,
String type1,
String type2,
boolean canBeNonNumeric)
{
int t1 = getNumericType(v1);
int t2 = getNumericType(v2);
if (t1 == NULL) {
t1 = getNumericTypeForName(type1);
}
if (t2 == NULL) {
t2 = getNumericTypeForName(type2);
}
if (canBeNonNumeric) {
// if the numeric type is NULL, we need to find out if the
// real type of v1 is non-numeric type
if (t1 == NULL &&
!StringUtil.nullOrEmptyOrBlankString(type1) &&
!PrimitiveTypeProvider.isNumericType(type1)) {
t1 = NONNUMERIC;
}
if (t2 == NULL &&
!StringUtil.nullOrEmptyOrBlankString(type2) &&
!PrimitiveTypeProvider.isNumericType(type2)) {
t2 = NONNUMERIC;
}
}
return getNumericType(t1, t2, canBeNonNumeric);
}
/**
* Returns a new Number object of an appropriate type to hold the given integer
* value. The type of the returned object is consistent with the given type
* argument, which is a constant from the NumericTypes interface.
*
* @param type the nominal numeric type of the result, a constant from the NumericTypes interface
* @param value the integer value to convert to a Number object
* @return a Number object with the given value, of type implied by the type argument
*/
public static Number newInteger (int type, long value)
{
switch ( type )
{
case NULL:
case BOOL:
case CHAR:
case INT:
return new Integer( (int)value );
case FLOAT:
if ( (long)(float)value == value ) {
return new Float( (float)value );
}
// else fall through:
case DOUBLE:
if ( (long)(double)value == value ) {
return new Double( (double)value );
}
// else fall through:
case LONG:
return new Long( value );
case BYTE:
return new Byte( (byte)value );
case SHORT:
return new Short( (short)value );
default:
return BigInteger.valueOf( value );
}
}
/**
* Returns a new Number object of an appropriate type to hold the given real value.
* The type of the returned object is always either Float or Double, and is only
* Float if the given type tag (a constant from the NumericTypes interface) is
* FLOAT.
*
* @param type the nominal numeric type of the result, a constant from the NumericTypes interface
* @param value the real value to convert to a Number object
* @return a Number object with the given value, of type implied by the type argument
*/
public static Number newReal (int type, double value)
{
if ( type == FLOAT ) {
return new Float( (float)value );
}
return new Double( value );
}
public static Object binaryOr (Object v1, Object v2)
{
int type = getNumericType(v1,v2);
if ( type == BIGINT || type == BIGDEC ) {
return bigIntValue(v1).or(bigIntValue(v2));
}
return newInteger( type, longValue(v1) | longValue(v2) );
}
public static Object binaryXor (Object v1, Object v2)
{
int type = getNumericType(v1,v2);
if ( type == BIGINT || type == BIGDEC ) {
return bigIntValue(v1).xor(bigIntValue(v2));
}
return newInteger( type, longValue(v1) ^ longValue(v2) );
}
public static Object binaryAnd (Object v1, Object v2)
{
int type = getNumericType(v1,v2);
if ( type == BIGINT || type == BIGDEC ) {
return bigIntValue(v1).and(bigIntValue(v2));
}
return newInteger( type, longValue(v1) & longValue(v2) );
}
public static boolean equal (Object v1, Object v2)
{
if (v1 == null) {
return (v2 == null);
}
if ( v1 == v2 || isEqual(v1, v2) ) {
return true;
}
if ( v1 instanceof Number && v2 instanceof Number ) {
return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
}
return false;
}
public static boolean less (Object v1, Object v2)
{
return compareWithConversion(v1, v2) < 0;
}
public static boolean greater (Object v1, Object v2)
{
return compareWithConversion(v1, v2) > 0;
}
public static boolean in (Object v1, Object v2)
{
if ( v2 == null ) {
// A null collection is always treated as empty
return false;
}
OrderedList listInterface = OrderedList.get(v2);
for (Iterator e = listInterface.elements(v2); e.hasNext();) {
Object o = e.next();
if ( equal(v1, o) ) {
return true;
}
}
return false;
}
public static Object shiftLeft (Object v1, Object v2)
{
int type = getNumericType(v1);
if (type == BIGINT || type == BIGDEC) {
return bigIntValue(v1).shiftLeft((int)longValue(v2));
}
return newInteger(type, longValue(v1) << (int)longValue(v2));
}
public static Object shiftRight (Object v1, Object v2)
{
int type = getNumericType(v1);
if ( type == BIGINT || type == BIGDEC ) {
return bigIntValue(v1).shiftRight((int)longValue(v2));
}
return newInteger(type, longValue(v1) >> (int)longValue(v2));
}
public static Object unsignedShiftRight (Object v1, Object v2)
{
int type = getNumericType(v1);
if ( type == BIGINT || type == BIGDEC ) {
return bigIntValue(v1).shiftRight((int)longValue(v2));
}
if ( type <= INT ) {
return newInteger(INT, ((int)longValue(v1)) >>> (int)longValue(v2));
}
return newInteger(type, longValue(v1) >>> (int)longValue(v2));
}
public static Object add (
Object v1,
Object v2,
String v1Type,
String v2Type)
{
int type = getNumericType(v1, v2, v1Type, v2Type, true);
switch ( type )
{
case BIGINT: return bigIntValue(v1).add(bigIntValue(v2));
case BIGDEC: return bigDecValue(v1).add(bigDecValue(v2));
case FLOAT:
case DOUBLE: return newReal( type, doubleValue(v1) + doubleValue(v2) );
case NONNUMERIC:
{
// if this is modified, please modify the getArithmeticOperations
// in TypeChecker as well
ArithmeticOperations ops = getArithmeticOperations(v1Type, v2Type);
if (ops != null) {
return ops.add(v1, v2);
}
// Now the operands are not numeric and they do not support
// ArithmeticOperations. The addition becomes concatenation.
return concatenate(v1, v2);
}
case CUSTOMNUMERICTYPE:
ArithmeticOperations operations =
getArithmeticOperations(v1Type, v2Type);
return operations.add(v1, v2);
default:
return newInteger( type, longValue(v1) + longValue(v2) );
}
}
public static Object concatenate (Object v1, Object v2)
{
return stringValue(v1) + stringValue(v2);
}
public static Object subtract (
Object v1,
Object v2,
String v1Type,
String v2Type)
{
int type = getNumericType(v1, v2, v1Type, v2Type, true);
switch ( type )
{
case BIGINT: return bigIntValue(v1).subtract(bigIntValue(v2));
case BIGDEC: return bigDecValue(v1).subtract(bigDecValue(v2));
case FLOAT:
case DOUBLE: return newReal( type, doubleValue(v1) - doubleValue(v2) );
case NONNUMERIC :
// if this is modified, please modify the getArithmeticOperations
// in TypeChecker as well
ArithmeticOperations ops = getArithmeticOperations(v1Type, v2Type);
if (ops != null) {
return ops.substract(v1, v2);
}
return newReal( type, doubleValue(v1) - doubleValue(v2) );
case CUSTOMNUMERICTYPE:
ArithmeticOperations operations =
getArithmeticOperations(v1Type, v2Type);
return operations.substract(v1, v2);
default:
return newInteger( type, longValue(v1) - longValue(v2) );
}
}
public static Object multiply (
Object v1,
Object v2,
String v1Type,
String v2Type)
{
int type = getNumericType(v1, v2, v1Type, v2Type, true);
ArithmeticOperations ops;
switch ( type )
{
case BIGINT: return bigIntValue(v1).multiply(bigIntValue(v2));
case BIGDEC: return bigDecValue(v1).multiply(bigDecValue(v2));
case FLOAT:
case DOUBLE: return newReal(type, doubleValue(v1) * doubleValue(v2));
case NONNUMERIC:
// if this is modified, please modify the getArithmeticOperations
// in TypeChecker as well
ops = getArithmeticOperations(v1Type);
if (ops != null) {
return ops.multiply(v1, bigDecValue(v2));
}
else {
ops = getArithmeticOperations(v2Type);
if (ops != null) {
return ops.multiply(v2, bigDecValue(v1));
}
}
return newReal(type, doubleValue(v1) * doubleValue(v2));
case CUSTOMNUMERICTYPE:
int t1 = getNumericType(v1), t2 = getNumericType(v2);
ops = getArithmeticOperations(v1Type, v2Type);
if (t1 > t2) {
return ops.multiply(v1, bigDecValue(v2));
}
else if (t2 > t1) {
return ops.multiply(v2, bigDecValue(v1));
}
// otherwise let the default case handle it
default :
return newInteger(type, longValue(v1) * longValue(v2));
}
}
public static Object divide (
Object v1,
Object v2,
String v1Type)
{
int type = getNumericType(v1, v2, v1Type, null, true);
ArithmeticOperations ops;
switch ( type )
{
case BIGINT:
return bigIntValue(v1).divide(bigIntValue(v2));
case BIGDEC:
return bigDecValue(v1).divide(bigDecValue(v2),
BigDecimal.ROUND_HALF_EVEN);
case FLOAT:
case DOUBLE:
return newReal(type, doubleValue(v1) / doubleValue(v2));
case NONNUMERIC :
// if this is modified, please modify the getArithmeticOperations
// in TypeChecker as well
ops = getArithmeticOperations(v1Type);
if (ops != null) {
return ops.divide(v1, v2);
}
return newReal(type, doubleValue(v1) / doubleValue(v2));
case CUSTOMNUMERICTYPE:
// support money/BigDecimal and
// money/money as in getting percentage
ops = getArithmeticOperations(v1Type);
return ops.divide(v1, v2);
default :
return newInteger(type, longValue(v1) / longValue(v2));
}
}
public static boolean isNull (Object v1)
{
return v1 == null;
}
/**
Returns the {@link ArithmeticOperations} for the given
<code>obj1</code> and <code>obj2</code>.
*/
private static ArithmeticOperations getArithmeticOperations (
Object obj1, Object obj2)
{
ArithmeticOperations op1 = getArithmeticOperations(obj1);
ArithmeticOperations op2 = getArithmeticOperations(obj2);
return getArithmeticOperationsInternal(op1, op2);
}
/**
Returns the {@link ArithmeticOperations} for the given
<code>obj</code>. If there is no {@link ArithmeticOperations}
for the given <code>obj</code>, <code>null</code> is returned.
*/
private static ArithmeticOperations getArithmeticOperations (Object obj)
{
ArithmeticOperations op = null;
if (obj != null) {
op = ArithmeticOperations.get(obj);
}
return op;
}
/**
Returns the {@link ArithmeticOperations} for the 2 operands
of the given <code>type1</code> and <code>type2</code>.
@aribaapi private
*/
public static ArithmeticOperations getArithmeticOperations (
String type1, String type2)
{
ArithmeticOperations op1 = getArithmeticOperations(type1);
ArithmeticOperations op2 = getArithmeticOperations(type2);
return getArithmeticOperationsInternal(op1, op2);
}
/**
Returns the {@link ArithmeticOperations} for the operand
of the given <code>type</code>. If there is no {@link ArithmeticOperations}
for the given <code>type</code>, <code>null</code> is returned.
*/
public static ArithmeticOperations getArithmeticOperations (String type)
{
ArithmeticOperations op = null;
if (!StringUtil.nullOrEmptyOrBlankString(type)) {
op = ArithmeticOperations.getByName(type);
}
return op;
}
private static ArithmeticOperations getArithmeticOperationsInternal (
ArithmeticOperations op1,
ArithmeticOperations op2)
{
if (op1 == null) {
return op2;
}
if (op2 == null) {
return op1;
}
if (op1 == op2) {
return op1;
}
return null;
}
public static Object remainder (Object v1, Object v2)
{
int type = getNumericType(v1,v2);
switch ( type )
{
case BIGDEC:
case BIGINT: return bigIntValue(v1).remainder(bigIntValue(v2));
default: return newInteger( type, longValue(v1) % longValue(v2) );
}
}
public static Object negate (Object value)
{
int type = getNumericType(value);
switch ( type )
{
case BIGINT: return bigIntValue(value).negate();
case BIGDEC: return bigDecValue(value).negate();
case FLOAT:
case DOUBLE: return newReal(type, -doubleValue(value));
default: return newInteger(type, -longValue(value));
}
}
public static Object bitNegate (Object value)
{
int type = getNumericType(value);
switch ( type )
{
case BIGDEC:
case BIGINT: return bigIntValue(value).not();
default: return newInteger(type, ~longValue(value));
}
}
}