net.sf.saxon.value.IntegerValue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.value;
import net.sf.saxon.functions.FormatNumber;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.str.UnicodeString;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ConversionResult;
import net.sf.saxon.type.ValidationFailure;
import net.sf.saxon.z.IntIterator;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* This class represents the XPath built-in type xs:integer. It is used for all
* subtypes of xs:integer, other than user-defined subtypes. There are two implementations
* of IntegerValue: {@link Int64Value}, which accommodates values up to 2^63, and
* {@link BigIntegerValue}, which accommodates unlimited-length integers.
* @since 9.8: changed in 9.8 to make this class a subclass of the new abstract
* class DecimalValue, to better reflect the XDM type hierarchy
*/
public abstract class IntegerValue extends DecimalValue {
/**
* Static data identifying the min and max values for each built-in subtype of xs:integer.
* This is a sequence of triples, each holding the fingerprint of the type, the minimum
* value, and the maximum value. The special value NO_LIMIT indicates that there is no
* minimum (or no maximum) for this type. The special value MAX_UNSIGNED_LONG represents the
* value 2^64-1
*/
private static final long NO_LIMIT = -9999;
private static final long MAX_UNSIGNED_LONG = -9998;
/*@NotNull*/ private static final long[] ranges = {
// arrange so the most frequently used types are near the start
StandardNames.XS_INTEGER, NO_LIMIT, NO_LIMIT,
StandardNames.XS_LONG, Long.MIN_VALUE, Long.MAX_VALUE,
StandardNames.XS_INT, Integer.MIN_VALUE, Integer.MAX_VALUE,
StandardNames.XS_SHORT, Short.MIN_VALUE, Short.MAX_VALUE,
StandardNames.XS_BYTE, Byte.MIN_VALUE, Byte.MAX_VALUE,
StandardNames.XS_NON_NEGATIVE_INTEGER, 0, NO_LIMIT,
StandardNames.XS_POSITIVE_INTEGER, 1, NO_LIMIT,
StandardNames.XS_NON_POSITIVE_INTEGER, NO_LIMIT, 0,
StandardNames.XS_NEGATIVE_INTEGER, NO_LIMIT, -1,
StandardNames.XS_UNSIGNED_LONG, 0, MAX_UNSIGNED_LONG,
StandardNames.XS_UNSIGNED_INT, 0, 4294967295L,
StandardNames.XS_UNSIGNED_SHORT, 0, 65535,
StandardNames.XS_UNSIGNED_BYTE, 0, 255};
/**
* Factory method: makes either an Int64Value or a BigIntegerValue depending on the value supplied
*
* @param value the supplied integer value
* @return the value as a BigIntegerValue or Int64Value as appropriate
*/
public static IntegerValue makeIntegerValue(/*@NotNull*/ BigInteger value) {
if (value.compareTo(BigIntegerValue.MAX_LONG) > 0 || value.compareTo(BigIntegerValue.MIN_LONG) < 0) {
return new BigIntegerValue(value);
} else {
return Int64Value.makeIntegerValue(value.longValue());
}
}
/**
* Convert a double to an integer
*
* @param value the double to be converted
* @return the result of the conversion, or the validation failure if the input is NaN or infinity
*/
public static ConversionResult fromDouble(double value) {
if (Double.isNaN(value)) {
ValidationFailure err = new ValidationFailure("Cannot convert double NaN to an integer");
err.setErrorCode("FOCA0002");
return err;
}
if (Double.isInfinite(value)) {
ValidationFailure err = new ValidationFailure("Cannot convert double INF to an integer");
err.setErrorCode("FOCA0002");
return err;
}
if (value > Long.MAX_VALUE || value < Long.MIN_VALUE) {
if (value == Math.floor(value)) {
return new BigIntegerValue(FormatNumber.adjustToDecimal(value, 2).toBigInteger());
} else {
return new BigIntegerValue(BigDecimal.valueOf(value).toBigInteger());
}
}
return Int64Value.makeIntegerValue((long) value);
}
/**
* This class allows subtypes of xs:integer to be held, as well as xs:integer values.
* This method sets the required type label. Note that this method modifies the value in situ.
*
* @param type the subtype of integer required
* @param validate true if validation is required, false if the caller warrants that the value
* is valid for the subtype
* @return null if the operation succeeds, or a ValidationException if the value is out of range
*/
/*@Nullable*/
public abstract ValidationFailure convertToSubType(BuiltInAtomicType type, boolean validate);
/**
* This class allows subtypes of xs:integer to be held, as well as xs:integer values.
* This method sets the required type label. Note that this method modifies the value in situ.
*
* @param type the subtype of integer required
* @return null if the operation succeeds, or a ValidationException if the value is out of range
*/
/*@Nullable*/
public abstract ValidationFailure validateAgainstSubType(BuiltInAtomicType type);
/**
* Check that a value is in range for the specified subtype of xs:integer
*
* @param value the value to be checked
* @param type the required item type, a subtype of xs:integer
* @return true if successful, false if value is out of range for the subtype
*/
public static boolean checkRange(long value, /*@NotNull*/ BuiltInAtomicType type) {
int fp = type.getFingerprint();
for (int i = 0; i < ranges.length; i += 3) {
if (ranges[i] == fp) {
long min = ranges[i + 1];
if (min != NO_LIMIT && value < min) {
return false;
}
long max = ranges[i + 2];
return max == NO_LIMIT || max == MAX_UNSIGNED_LONG || value <= max;
}
}
throw new IllegalArgumentException(
"No range information found for integer subtype " + type.getDescription());
}
/**
* Get the minInclusive facet for a built-in integer subtype
*
* @param type the built-in type, which must be derived from xs:integer
* @return the value of the minInclusive facet if there is a lower limit, or null if not
*/
public static IntegerValue getMinInclusive(BuiltInAtomicType type) {
int fp = type.getFingerprint();
for (int i = 0; i < ranges.length; i += 3) {
if (ranges[i] == fp) {
long min = ranges[i + 1];
if (min == NO_LIMIT) {
return null;
} else {
return Int64Value.makeIntegerValue(min);
}
}
}
return null;
}
/**
* Get the maxInclusive facet for a built-in integer subtype
*
* @param type the built-in type, which must be derived from xs:integer
* @return the value of the minInclusive facet if there is a lower limit, or null if not
*/
public static IntegerValue getMaxInclusive(BuiltInAtomicType type) {
int fp = type.getFingerprint();
for (int i = 0; i < ranges.length; i += 3) {
if (ranges[i] == fp) {
long max = ranges[i + 2];
if (max == NO_LIMIT) {
return null;
} else if (max == MAX_UNSIGNED_LONG) {
return IntegerValue.makeIntegerValue(BigIntegerValue.MAX_UNSIGNED_LONG);
} else {
return Int64Value.makeIntegerValue(max);
}
}
}
return null;
}
/**
* Check that a BigInteger is within the required range for a given integer subtype.
* This method is expensive, so it should not be used unless the BigInteger is outside the range of a long.
*
* @param big the supplied BigInteger
* @param type the derived type (a built-in restriction of xs:integer) to check the value against
* @return true if the value is within the range for the derived type
*/
public static boolean checkBigRange(BigInteger big, /*@NotNull*/ BuiltInAtomicType type) {
for (int i = 0; i < ranges.length; i += 3) {
if (ranges[i] == type.getFingerprint()) {
long min = ranges[i + 1];
if (min != NO_LIMIT && BigInteger.valueOf(min).compareTo(big) > 0) {
return false;
}
long max = ranges[i + 2];
if (max == NO_LIMIT) {
return true;
} else if (max == MAX_UNSIGNED_LONG) {
return BigIntegerValue.MAX_UNSIGNED_LONG.compareTo(big) >= 0;
} else {
return BigInteger.valueOf(max).compareTo(big) >= 0;
}
}
}
throw new IllegalArgumentException(
"No range information found for integer subtype " + type.getDescription());
}
/**
* Static factory method to convert strings to integers.
*
* @param s the string to be converted
* @return either an Int64Value or a BigIntegerValue representing the value of the String, or
* a ValidationFailure encapsulating an Exception if the value cannot be converted.
*/
public static ConversionResult stringToInteger(String s) {
int len = s.length();
int start = 0;
int last = len - 1;
while (start < len && s.charAt(start) <= 0x20) {
start++;
}
while (last > start && s.charAt(last) <= 0x20) {
last--;
}
if (start > last) {
return new ValidationFailure("Cannot convert zero-length string to an integer");
}
if (last - start < 16) {
// for short numbers, we do the conversion ourselves, to avoid throwing unnecessary exceptions
boolean negative = false;
long value = 0;
int i = start;
if (s.charAt(i) == '+') {
i++;
} else if (s.charAt(i) == '-') {
negative = true;
i++;
}
if (i > last) {
return new ValidationFailure("Cannot convert string " + Err.wrap(s, Err.VALUE) +
" to integer: no digits after the sign");
}
while (i <= last) {
int d = s.charAt(i++);
if (d >= '0' && d <= '9') {
value = 10 * value + (d - '0');
} else {
return new ValidationFailure("Cannot convert string " + Err.wrap(s, Err.VALUE) + " to an integer");
}
}
return Int64Value.makeIntegerValue(negative ? -value : value);
} else {
// for longer numbers, rely on library routines
try {
if (start > 0 || last < len-1) {
s = s.substring(start, last+1);
}
if (s.charAt(0) == '+') {
s = s.substring(1);
}
if (s.length() < 16) {
return new Int64Value(Long.parseLong(s));
} else {
return new BigIntegerValue(new BigInteger(s));
}
} catch (NumberFormatException err) {
return new ValidationFailure("Cannot convert string " + Err.wrap(s, Err.VALUE) + " to an integer");
}
}
}
/**
* Determine whether a string is castable as an integer
*
* @param input the string to be tested
* @return null if the string is castable to an integer, or a validation failure otherwise
*/
/*@Nullable*/
public static ValidationFailure castableAsInteger(UnicodeString input) {
IntIterator iter = input.codePoints();
int state = 0; // 0 - initial whitespace;
// 1 - expecting digits;
// 2 - expecting digits or final whitespace or EOS
// 3 - expecting final whitespace or EOS
while (iter.hasNext()) {
int c = iter.next();
switch (state) {
case 0:
if (Whitespace.isWhite(c)) {
state = 0;
} else if (c == '+' || c == '-') {
state = 1;
} else if (c >= '0' && c <= '9') {
state = 2;
} else {
return new ValidationFailure("Cannot convert string " + Err.wrap(input, Err.VALUE) + " to an integer: contains a character that is not a digit");
}
break;
case 1:
if (c >= '0' && c <= '9') {
state = 2;
} else {
return new ValidationFailure("Cannot convert string " + Err.wrap(input, Err.VALUE) + " to an integer: expected a digit, found " + c);
}
break;
case 2:
if (c >= '0' && c <= '9') {
state = 2;
} else if (Whitespace.isWhite(c)) {
state = 3;
} else {
return new ValidationFailure("Cannot convert string " + Err.wrap(input, Err.VALUE) + " to an integer: expected a digit, found " + c);
}
break;
case 3:
if (Whitespace.isWhite(c)) {
state = 3;
} else {
return new ValidationFailure("Cannot convert string " + Err.wrap(input, Err.VALUE) + " to an integer: found " + c + " after final whitespace");
}
break;
default:
throw new IllegalStateException();
}
}
if (state == 0 || state == 1) {
return new ValidationFailure("Cannot convert string " + Err.wrap(input, Err.VALUE) + " to an integer: no digits found");
}
return null;
}
/**
* Determine the primitive type of the value. This delivers the same answer as
* getItemType().getPrimitiveItemType(). The primitive types are
* the 19 primitive types of XML Schema, plus xs:integer, xs:dayTimeDuration and xs:yearMonthDuration,
* and xs:untypedAtomic. For external objects, the result is AnyAtomicType.
*/
/*@NotNull*/
@Override
public BuiltInAtomicType getPrimitiveType() {
return BuiltInAtomicType.INTEGER;
}
/**
* Get the numeric value converted to a decimal
*
* @return a decimal representing this numeric value;
*/
@Override
public abstract BigDecimal getDecimalValue();
/**
* Determine whether the value is a whole number, that is, whether it compares
* equal to some integer
*
* @return always true for this implementation
*/
@Override
public boolean isWholeNumber() {
return true;
}
/**
* Add another integer
*
* @param other the other integer
* @return the result of the addition
*/
public abstract IntegerValue plus(IntegerValue other);
/**
* Subtract another integer
*
* @param other the other integer
* @return the result of the subtraction
*/
public abstract IntegerValue minus(IntegerValue other);
/**
* Multiply by another integer
*
* @param other the other integer
* @return the result of the multiplication
*/
public abstract IntegerValue times(IntegerValue other);
/**
* Divide by another integer
*
* @param other the other integer
* @return the result of the division
* @throws XPathException if the other integer is zero
*/
public abstract NumericValue div(IntegerValue other) throws XPathException;
/**
* Divide by another integer, providing location information for any exception
*
* @param other the other integer
* @param locator the location of the expression, for use in diagnostics
* @return the result of the division
* @throws XPathException if the other integer is zero
*/
public NumericValue div(IntegerValue other, Location locator) throws XPathException {
try {
return div(other);
} catch (XPathException err) {
err.maybeSetLocation(locator);
throw err;
}
}
/**
* Take modulo another integer
*
* @param other the other integer
* @return the result of the modulo operation (the remainder)
* @throws XPathException if the other integer is zero
*/
public abstract IntegerValue mod(IntegerValue other) throws XPathException;
/**
* Take modulo another integer, providing location information for any exception
*
* @param other the other integer
* @param locator the location of the expression, for use in diagnostics
* @return the result of the division
* @throws XPathException if the other integer is zero
*/
public IntegerValue mod(IntegerValue other, Location locator) throws XPathException {
try {
return mod(other);
} catch (XPathException err) {
err.maybeSetLocation(locator);
throw err;
}
}
/**
* Integer divide by another integer
*
* @param other the other integer
* @return the result of the integer division
* @throws XPathException if the other integer is zero
*/
public abstract IntegerValue idiv(IntegerValue other) throws XPathException;
/**
* Integer divide by another integer, providing location information for any exception
*
* @param other the other integer
* @param locator the location of the expression, for use in diagnostics
* @return the result of the division
* @throws XPathException if the other integer is zero
*/
public IntegerValue idiv(IntegerValue other, Location locator) throws XPathException {
try {
return idiv(other);
} catch (XPathException err) {
err.maybeSetLocation(locator);
throw err;
}
}
/**
* Get the value as a BigInteger
*
* @return the value, as a BigInteger
*/
public abstract BigInteger asBigInteger();
/**
* Get the signum of an int
*
* @param i the int
* @return -1 if the integer is negative, 0 if it is zero, +1 if it is positive
*/
protected static int signum(int i) {
return (i >> 31) | (-i >>> 31);
}
/**
* Determine whether two atomic values are identical, as determined by XML Schema rules. This is a stronger
* test than equality (even schema-equality); for example two dateTime values are not identical unless
* they are in the same timezone.
* Note that even this check ignores the type annotation of the value. The integer 3 and the short 3
* are considered identical, even though they are not fully interchangeable. "Identical" means the
* same point in the value space, regardless of type annotation.
* NaN is identical to itself.
*
* @param v the other value to be compared with this one
* @return true if the two values are identical, false otherwise.
*/
@Override
public boolean isIdentical(/*@NotNull*/ AtomicValue v) {
return (v instanceof IntegerValue) && equals(v);
}
}