/*
* 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 org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.Collator;
/**
* [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 {
//TODO this class should be split into numerous sub classes for each xs: type with proper
//inheritance as defined by http://www.w3.org/TR/xmlschema-2/#built-in-datatypes
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("9223372036854775807");
private static final BigInteger SMALLEST_LONG = new BigInteger("-9223372036854775808");
private static final BigInteger LARGEST_UNSIGNED_LONG = new BigInteger("18446744073709551615");
private static final BigInteger LARGEST_INT = new BigInteger("2147483647");
private static final BigInteger SMALLEST_INT = new BigInteger("-2147483648");
private static final BigInteger LARGEST_UNSIGNED_INT = new BigInteger("4294967295");
private static final BigInteger LARGEST_SHORT = new BigInteger("32767");
private static final BigInteger SMALLEST_SHORT = new BigInteger("-32768");
private static final BigInteger LARGEST_UNSIGNED_SHORT = new BigInteger("65535");
private static final BigInteger LARGEST_BYTE = new BigInteger("127");
private static final BigInteger SMALLEST_BYTE = new BigInteger("-128");
private static final BigInteger LARGEST_UNSIGNED_BYTE = new BigInteger("255");
private BigInteger value;
// private long value;
//should default type be NUMBER or LONG ? -shabanovd
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 (final NumberFormatException e) {
throw new XPathException(ErrorCodes.FORG0001,
"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(ErrorCodes.FORG0001, "can not convert '" +
stringValue + "' to " + Type.getTypeName(type));
}
} catch (final NumberFormatException e) {
throw new XPathException(ErrorCodes.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;
}
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));
}
/**
* @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) >= 0 &&
value.compareTo(LARGEST_LONG) <= 0;
case Type.UNSIGNED_LONG:
return value.compareTo(ZERO_BIGINTEGER) >= 0 &&
value.compareTo(LARGEST_UNSIGNED_LONG) <= 0;
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) >= 0 &&
value.compareTo(LARGEST_INT) <= 0;
case Type.UNSIGNED_INT:
return value.compareTo(ZERO_BIGINTEGER) >= 0 &&
value.compareTo(LARGEST_UNSIGNED_INT) <= 0;
case Type.SHORT:
return value.compareTo(SMALLEST_SHORT) >= 0 &&
value.compareTo(LARGEST_SHORT) <= 0;
case Type.UNSIGNED_SHORT:
return value.compareTo(ZERO_BIGINTEGER) >= 0 &&
value.compareTo(LARGEST_UNSIGNED_SHORT) <= 0;
case Type.BYTE:
return value.compareTo(SMALLEST_BYTE) >= 0 &&
value.compareTo(LARGEST_BYTE) <= 0;
case Type.UNSIGNED_BYTE:
return value.compareTo(ZERO_BIGINTEGER) >= 0 &&
value.compareTo(LARGEST_UNSIGNED_BYTE) <= 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 {
if (this.type == requiredType) {
return this;
}
switch (requiredType) {
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.NUMBER:
case Type.LONG:
case Type.INTEGER:
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(ErrorCodes.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 == null) {
return round();
}
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(ErrorCodes.FOAR0001, "division by zero");
}
//http://www.w3.org/TR/xpath20/#mapping : numeric; but xs:decimal if both operands are xs:integer
final BigDecimal d = new BigDecimal(value);
final BigDecimal od = new BigDecimal(((IntegerValue) other).value);
final 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(ErrorCodes.FOAR0001, "division by zero");
}
final 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)) {
if (other.isZero()) {
throw new XPathException(ErrorCodes.FOAR0001, "division by zero");
}
// long ov = ((IntegerValue) other).value.longValue();
final BigInteger ov = ((IntegerValue) other).value;
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 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 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)
*/
@Override
public <T> T toJavaObject(final Class<T> target) throws XPathException {
if (target.isAssignableFrom(IntegerValue.class)) {
return (T) this;
} else if (target == Long.class || target == long.class) {
// ?? jmv: return new Long(value);
return (T) Long.valueOf(value.longValue());
} else if (target == Integer.class || target == int.class) {
final IntegerValue v = (IntegerValue) convertTo(Type.INT);
return (T) Integer.valueOf(v.value.intValue());
} else if (target == Short.class || target == short.class) {
final IntegerValue v = (IntegerValue) convertTo(Type.SHORT);
return (T) Short.valueOf(v.value.shortValue());
} else if (target == Byte.class || target == byte.class) {
final IntegerValue v = (IntegerValue) convertTo(Type.BYTE);
return (T) Byte.valueOf(v.value.byteValue());
} else if (target == Double.class || target == double.class) {
final DoubleValue v = (DoubleValue) convertTo(Type.DOUBLE);
return (T) Double.valueOf(v.getValue());
} else if (target == Float.class || target == float.class) {
final FloatValue v = (FloatValue) convertTo(Type.FLOAT);
return (T) Float.valueOf(v.value);
} else if (target == Boolean.class || target == boolean.class) {
return (T) new BooleanValue(effectiveBooleanValue());
} else if (target == String.class) {
return (T) value.toString();
} else if (target == BigInteger.class) {
return (T) new BigInteger(value.toByteArray());
} else if (target == Object.class) {
return (T) 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;
}
}
@Override
public int hashCode() {
return value.hashCode();
}
}