/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2007 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.xquery.value;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.Collator;
import org.exist.xquery.Constants;
import org.exist.xquery.XPathException;
/** [Definition:] integer is <i>derived</i> from decimal by fixing the value of <i>fractionDigits<i> to be 0.
* This results in the standard mathematical concept of the integer numbers.
* The <i>value space</i> of integer is the infinite set {...,-2,-1,0,1,2,...}.
* The <i>base type</i> of integer is decimal.
* cf http://www.w3.org/TR/xmlschema-2/#integer
*/
public class IntegerValue extends NumericValue {
public final static IntegerValue ZERO = new IntegerValue(0);
private static final BigInteger ZERO_BIGINTEGER = new BigInteger("0");
private static final BigInteger ONE_BIGINTEGER = new BigInteger("1");
private static final BigInteger MINUS_ONE_BIGINTEGER = new BigInteger("-1");
private static final BigInteger LARGEST_LONG = new BigInteger("9223372036854775808");
private static final BigInteger SMALLEST_LONG = LARGEST_LONG.negate();
private static final BigInteger LARGEST_INT = new BigInteger("4294967296");
private static final BigInteger SMALLEST_INT = LARGEST_INT.negate();
private static final BigInteger LARGEST_SHORT = new BigInteger("65536");
private static final BigInteger SMALLEST_SHORT = LARGEST_SHORT.negate();
private static final BigInteger LARGEST_BYTE = new BigInteger("256");
private static final BigInteger SMALLEST_BYTE = LARGEST_BYTE.negate();
private BigInteger value;
// private long value;
private int type = Type.INTEGER;
public IntegerValue(long value) {
this.value = BigInteger.valueOf(value); // new BigInteger(value);
}
public IntegerValue(long value, int type) throws XPathException {
this(value);
this.type = type;
if (!checkType(value, type))
throw new XPathException(
"Value is not a valid integer for type " + Type.getTypeName(type));
}
public IntegerValue(String stringValue) throws XPathException {
try {
value = new BigInteger(StringValue.trimWhitespace(stringValue)); // Long.parseLong(stringValue);
} catch (NumberFormatException e) {
throw new XPathException(
"failed to convert '" + stringValue + "' to an integer: " + e.getMessage(), e);
// }
}
}
public IntegerValue(String stringValue, int requiredType) throws XPathException {
this.type = requiredType;
try {
value = new BigInteger(StringValue.trimWhitespace(stringValue)); // Long.parseLong(stringValue);
if (!(checkType(value, type)))
throw new XPathException("FORG0001: can not convert '" +
stringValue + "' to " + Type.getTypeName(type));
} catch (NumberFormatException e) {
throw new XPathException("FORG0001: can not convert '" +
stringValue + "' to " + Type.getTypeName(type));
}
}
/**
* @param value
* @param requiredType
*/
public IntegerValue(BigInteger value, int requiredType) {
this.value = value;
type = requiredType;
}
/**
* @param integer
*/
public IntegerValue(BigInteger integer) {
this.value = integer;
}
/**
* @param value2
* @param type2
* @throws XPathException
*/
private boolean checkType(BigInteger value2, int type2) throws XPathException {
switch (type) {
case Type.LONG :
// jmv: add test since now long is not the default implementation anymore:
return value.compareTo(SMALLEST_LONG) != Constants.INFERIOR &&
value.compareTo(LARGEST_LONG ) != Constants.SUPERIOR;
case Type.INTEGER :
case Type.DECIMAL :
return true;
case Type.POSITIVE_INTEGER :
return value.compareTo(ZERO_BIGINTEGER) == 1; // >0
case Type.NON_NEGATIVE_INTEGER :
return value.compareTo(MINUS_ONE_BIGINTEGER) == 1; // > -1
case Type.NEGATIVE_INTEGER :
return value.compareTo(ZERO_BIGINTEGER) == -1 ; // <0
case Type.NON_POSITIVE_INTEGER :
return value.compareTo(ONE_BIGINTEGER) == -1; // <1
case Type.INT :
return value.compareTo(SMALLEST_INT) == 1 &&
value.compareTo(LARGEST_INT) == -1;
case Type.SHORT :
return value.compareTo(SMALLEST_SHORT) == 1 &&
value.compareTo(LARGEST_SHORT) == -1;
case Type.BYTE :
return value.compareTo(SMALLEST_BYTE) == 1 &&
value.compareTo(LARGEST_BYTE) == -1;
case Type.UNSIGNED_LONG :
return value.compareTo(MINUS_ONE_BIGINTEGER) == 1 &&
value.compareTo(LARGEST_LONG ) == -1;
case Type.UNSIGNED_INT:
return value.compareTo(MINUS_ONE_BIGINTEGER) == 1 &&
value.compareTo(LARGEST_INT) == -1;
case Type.UNSIGNED_SHORT :
return value.compareTo(MINUS_ONE_BIGINTEGER) == 1 &&
value.compareTo(LARGEST_SHORT) == -1;
case Type.UNSIGNED_BYTE :
return value.compareTo(MINUS_ONE_BIGINTEGER) == 1 &&
value.compareTo(LARGEST_BYTE) == -1;
}
throw new XPathException("Unknown type: " + Type.getTypeName(type));
}
private final static boolean checkType(long value, int type) throws XPathException {
switch (type) {
case Type.LONG :
case Type.INTEGER :
case Type.DECIMAL :
return true;
case Type.NON_POSITIVE_INTEGER :
return value < 1;
case Type.NEGATIVE_INTEGER :
return value < 0;
case Type.INT :
return value >= -4294967295L && value <= 4294967295L;
case Type.SHORT :
return value >= -65535 && value <= 65535;
case Type.BYTE :
return value >= -255 && value <= 255;
case Type.NON_NEGATIVE_INTEGER :
return value > -1;
case Type.UNSIGNED_LONG :
return value > -1;
case Type.UNSIGNED_INT:
return value > -1 && value <= 4294967295L;
case Type.UNSIGNED_SHORT :
return value > -1 && value <= 65535;
case Type.UNSIGNED_BYTE :
return value > -1 && value <= 255;
case Type.POSITIVE_INTEGER :
return value > 0; // jmv >= 0;
}
throw new XPathException("Unknown type: " + Type.getTypeName(type));
}
/* (non-Javadoc)
* @see org.exist.xquery.value.AtomicValue#getType()
*/
public int getType() {
return type;
}
public boolean hasFractionalPart() {
return false;
};
public Item itemAt(int pos) {
return pos == 0 ? this : null;
}
public long getValue() {
return value.longValue();
}
public void setValue(long value) {
this.value = BigInteger.valueOf(value);
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Item#getStringValue()
*/
public String getStringValue() {
return // Long.toString(value);
value.toString();
}
public boolean isNaN() {
return false;
}
public boolean isInfinite() {
return false;
}
public boolean isZero() {
return value.signum() == 0;
//return value.compareTo(ZERO_BIGINTEGER) == Constants.EQUAL;
};
public boolean isNegative() {
return value.signum()<0;
}
public boolean isPositive() {
return value.signum()>0;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.AtomicValue#convertTo(int)
*/
public AtomicValue convertTo(int requiredType) throws XPathException {
switch (requiredType) {
case Type.NUMBER :
case Type.INTEGER :
case Type.LONG :
case Type.ATOMIC :
case Type.ITEM :
return this;
case Type.DECIMAL :
return new DecimalValue(new BigDecimal(value));
case Type.UNTYPED_ATOMIC :
return new UntypedAtomicValue(getStringValue());
case Type.NON_POSITIVE_INTEGER :
case Type.NEGATIVE_INTEGER :
case Type.INT :
case Type.SHORT :
case Type.BYTE :
case Type.NON_NEGATIVE_INTEGER :
case Type.UNSIGNED_LONG :
case Type.UNSIGNED_INT :
case Type.UNSIGNED_SHORT :
case Type.UNSIGNED_BYTE :
case Type.POSITIVE_INTEGER :
return new IntegerValue(value, requiredType);
case Type.DOUBLE :
return new DoubleValue(value.doubleValue());
case Type.FLOAT:
return new FloatValue(value.floatValue());
case Type.STRING :
return new StringValue(getStringValue());
case Type.BOOLEAN :
return (value.compareTo(ZERO_BIGINTEGER) == 0 ) ? BooleanValue.FALSE : BooleanValue.TRUE;
default :
throw new XPathException(
"err:FORG0001: cannot convert '"
+ Type.getTypeName(this.getType())
+ " ("
+ value
+ ")' into "
+ Type.getTypeName(requiredType));
}
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#getInt()
*/
public int getInt() throws XPathException {
return value.intValue(); // (int) value;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#getLong()
*/
public long getLong() throws XPathException {
return value.longValue();
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#getDouble()
*/
public double getDouble() throws XPathException {
return value.doubleValue(); // (double) value;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#ceiling()
*/
public NumericValue ceiling() throws XPathException {
return this;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#floor()
*/
public NumericValue floor() throws XPathException {
return this;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#round()
*/
public NumericValue round() throws XPathException {
return this;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#round(org.exist.xquery.IntegerValue)
*/
public NumericValue round(IntegerValue precision) throws XPathException {
if ( precision.getInt()<=0 )
return (IntegerValue) ((DecimalValue) convertTo(Type.DECIMAL)).round(precision).convertTo(Type.INTEGER);
else
return this;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#minus(org.exist.xquery.value.NumericValue)
*/
public ComputableValue minus(ComputableValue other) throws XPathException {
if (Type.subTypeOf(other.getType(), Type.INTEGER))
// return new IntegerValue(value - ((IntegerValue) other).value, type);
return new IntegerValue( value.subtract( ((IntegerValue) other).value ), type );
else
return ((ComputableValue) convertTo(other.getType())).minus(other);
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#plus(org.exist.xquery.value.NumericValue)
*/
public ComputableValue plus(ComputableValue other) throws XPathException {
if (Type.subTypeOf(other.getType(), Type.INTEGER))
// return new IntegerValue(value + ((IntegerValue) other).value, type);
return new IntegerValue( value.add( ((IntegerValue) other).value ), type );
else
return ((ComputableValue) convertTo(other.getType())).plus(other);
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#mult(org.exist.xquery.value.NumericValue)
*/
public ComputableValue mult(ComputableValue other) throws XPathException {
if(Type.subTypeOf(other.getType(), Type.INTEGER))
return new IntegerValue( value.multiply( ((IntegerValue) other).value ), type );
else if(Type.subTypeOf(other.getType(), Type.DURATION))
return other.mult(this);
else
return ((ComputableValue) convertTo(other.getType())).mult(other);
}
/** The div operator performs floating-point division according to IEEE 754.
* @see org.exist.xquery.value.NumericValue#idiv(org.exist.xquery.value.NumericValue)
*/
public ComputableValue div(ComputableValue other) throws XPathException {
if (other instanceof IntegerValue) {
if (((IntegerValue) other).isZero())
throw new XPathException("FOAR0001 : division by zero");
//http://www.w3.org/TR/xpath20/#mapping : numeric; but xs:decimal if both operands are xs:integer
BigDecimal d = new BigDecimal(value);
BigDecimal od = new BigDecimal(((IntegerValue) other).value);
int scale = Math.max(18, Math.max(d.scale(), od.scale()));
return new DecimalValue(d.divide(od, scale, BigDecimal.ROUND_HALF_DOWN));
} else
//TODO : review type promotion
return ((ComputableValue) convertTo(other.getType())).div(other);
}
public IntegerValue idiv(NumericValue other) throws XPathException {
if (other.isZero())
//If the divisor is (positive or negative) zero, then an error is raised [err:FOAR0001]
throw new XPathException("FOAR0001: division by zero");
ComputableValue result = div(other);
return new IntegerValue(((IntegerValue)result.convertTo(Type.INTEGER)).getLong());
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#mod(org.exist.xquery.value.NumericValue)
*/
public NumericValue mod(NumericValue other) throws XPathException {
if (Type.subTypeOf(other.getType(), Type.INTEGER)) {
// long ov = ((IntegerValue) other).value.longValue();
BigInteger ov = ((IntegerValue) other).value;
if( ! ((IntegerValue) other).effectiveBooleanValue() )
// if (ov == 0)
throw new XPathException("division by zero");
return new IntegerValue(value.remainder(ov), type);
} else
return ((NumericValue) convertTo(other.getType())).mod(other);
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#unaryMinus()
*/
public NumericValue negate() throws XPathException {
return new IntegerValue(value.negate());
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#abs()
*/
public NumericValue abs() throws XPathException {
// return new IntegerValue(Math.abs(value), type);
return new IntegerValue( value.abs(), type);
}
/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#max(org.exist.xquery.value.AtomicValue)
*/
public AtomicValue max(Collator collator, AtomicValue other) throws XPathException {
if(Type.subTypeOf(other.getType(), Type.INTEGER))
return new IntegerValue( value.max( ((IntegerValue) other).value) );
else
return ((NumericValue) convertTo(other.getType())).max(collator, other);
}
public AtomicValue min(Collator collator, AtomicValue other) throws XPathException {
if(Type.subTypeOf(other.getType(), Type.INTEGER))
return new IntegerValue( value.min( ((IntegerValue) other).value) );
else
return ((NumericValue) convertTo(other.getType())).min(collator, other);
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Item#conversionPreference(java.lang.Class)
*/
public int conversionPreference(Class javaClass) {
if(javaClass.isAssignableFrom(IntegerValue.class)) return 0;
if(javaClass == Long.class || javaClass == long.class) return 1;
if(javaClass == Integer.class || javaClass == int.class) return 2;
if(javaClass == Short.class || javaClass == short.class) return 3;
if(javaClass == Byte.class || javaClass == byte.class) return 4;
if(javaClass == Double.class || javaClass == double.class) return 5;
if(javaClass == Float.class || javaClass == float.class) return 6;
if(javaClass == String.class) return 7;
if(javaClass == Boolean.class || javaClass == boolean.class) return 8;
if(javaClass == Object.class) return 20;
return Integer.MAX_VALUE;
}
/* (non-Javadoc)
* @see org.exist.xquery.value.Item#toJavaObject(java.lang.Class)
*/
public Object toJavaObject(Class target) throws XPathException {
if(target.isAssignableFrom(IntegerValue.class))
return this;
else if(target == Long.class || target == long.class)
// ?? jmv: return new Long(value);
return new Long(value.longValue());
else if(target == Integer.class || target == int.class) {
IntegerValue v = (IntegerValue)convertTo(Type.INT);
return new Integer((int)v.value.intValue());
} else if(target == Short.class || target == short.class) {
IntegerValue v = (IntegerValue)convertTo(Type.SHORT);
return new Short((short)v.value.shortValue());
} else if(target == Byte.class || target == byte.class) {
IntegerValue v = (IntegerValue)convertTo(Type.BYTE);
return new Byte((byte)v.value.byteValue());
} else if(target == Double.class || target == double.class) {
DoubleValue v = (DoubleValue)convertTo(Type.DOUBLE);
return new Double(v.getValue());
} else if(target == Float.class || target == float.class) {
FloatValue v = (FloatValue)convertTo(Type.FLOAT);
return new Float(v.value);
} else if(target == Boolean.class || target == boolean.class)
return new BooleanValue(effectiveBooleanValue());
else if(target == String.class)
// return Long.toString(value);
return value.toString();
else if(target == Object.class)
return value; // Long(value);
throw new XPathException("cannot convert value of type " + Type.getTypeName(getType()) +
" to Java object of type " + target.getName());
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Object o) {
final AtomicValue other = (AtomicValue)o;
if(Type.subTypeOf(other.getType(), Type.INTEGER))
return value.compareTo(((IntegerValue)other).value);
else
return getType() > other.getType() ? 1 : -1;
}
}