001: package net.sf.saxon.value;
002:
003: import net.sf.saxon.Err;
004: import net.sf.saxon.expr.Token;
005: import net.sf.saxon.expr.XPathContext;
006: import net.sf.saxon.trans.DynamicError;
007: import net.sf.saxon.trans.XPathException;
008: import net.sf.saxon.type.*;
009:
010: import java.math.BigDecimal;
011: import java.math.BigInteger;
012:
013: /**
014: * An integer value: note this is a subtype of decimal in XML Schema, not a primitive type.
015: * This class also supports the built-in subtypes of xs:integer.
016: * Actually supports a value in the range permitted by a Java "long"
017: */
018:
019: public final class IntegerValue extends NumericValue {
020:
021: /**
022: * IntegerValue representing the value -1
023: */
024: public static final IntegerValue MINUS_ONE = new IntegerValue(-1);
025: /**
026: * IntegerValue representing the value zero
027: */
028: public static final IntegerValue ZERO = new IntegerValue(0);
029: /**
030: * IntegerValue representing the value +1
031: */
032: public static final IntegerValue PLUS_ONE = new IntegerValue(+1);
033: /**
034: * IntegerValue representing the maximum value for this class
035: */
036: public static final IntegerValue MAX_LONG = new IntegerValue(
037: Long.MAX_VALUE);
038: /**
039: * IntegerValue representing the minimum value for this class
040: */
041: public static final IntegerValue MIN_LONG = new IntegerValue(
042: Long.MIN_VALUE);
043:
044: private long value;
045: private ItemType type;
046:
047: /**
048: * Constructor supplying a long
049: *
050: * @param value the value of the IntegerValue
051: */
052:
053: public IntegerValue(long value) {
054: this .value = value;
055: this .type = Type.INTEGER_TYPE;
056: }
057:
058: /**
059: * Constructor for a subtype, supplying an integer
060: *
061: * @param val The supplied value, as an integer
062: * @param type the required item type, a subtype of xs:integer
063: * @exception DynamicError if the supplied value is out of range for the
064: * target type
065: */
066:
067: public IntegerValue(long val, AtomicType type) throws DynamicError {
068: this .value = val;
069: this .type = type;
070: if (!checkRange(value, type)) {
071: DynamicError err = new DynamicError("Integer value " + val
072: + " is out of range for the requested type "
073: + type.getDescription());
074: err.setErrorCode("XPTY0004");
075: err.setIsTypeError(true);
076: throw err;
077: }
078: ;
079: }
080:
081: /**
082: * Convert the value to a subtype of xs:integer
083: * @param subtype the target subtype
084: * @param validate true if validation is required; false if the caller already knows that the value is valid
085: * @return null if the conversion succeeds; a ValidationException describing the failure if it fails. Note
086: * that the exception is returned, not thrown.
087: */
088:
089: public ValidationException convertToSubtype(AtomicType subtype,
090: boolean validate) {
091: if (!validate) {
092: setSubType(subtype);
093: return null;
094: } else if (checkRange(subtype)) {
095: return null;
096: } else {
097: ValidationException err = new ValidationException("String "
098: + value
099: + " cannot be converted to integer subtype "
100: + subtype.getDescription());
101: err.setErrorCode("FORG0001");
102: return err;
103: }
104: }
105:
106: /**
107: * This class allows subtypes of xs:integer to be held, as well as xs:integer values.
108: * This method sets the required type label. It is the caller's responsibility to check that
109: * the value is within range.
110: */
111: public void setSubType(AtomicType type) {
112: this .type = type;
113: }
114:
115: /**
116: * This class allows subtypes of xs:integer to be held, as well as xs:integer values.
117: * This method checks that the value is within range, and also sets the type label.
118: * @param type the subtype of integer required
119: * @return true if successful, false if value is out of range for the subtype
120: */
121: public boolean checkRange(AtomicType type) {
122: this .type = type;
123: return checkRange(value, type);
124: }
125:
126: /**
127: * Static factory method to convert strings to integers.
128: * @param s the String to be converted
129: * @return either an IntegerValue or a BigIntegerValue representing the value of the String, or
130: * an ErrorValue encapsulating an Exception if the value cannot be converted.
131: */
132:
133: public static AtomicValue stringToInteger(CharSequence s) {
134:
135: int len = s.length();
136: int start = 0;
137: while (start < len && s.charAt(start) <= 0x20) {
138: start++;
139: }
140: int last = len - 1;
141: while (last > start && s.charAt(last) <= 0x20) {
142: last--;
143: }
144: if (start > last) {
145: return numericError("Cannot convert zero-length string to an integer");
146: }
147: if (last - start < 16) {
148: // for short numbers, we do the conversion ourselves, to avoid throwing unnecessary exceptions
149: boolean negative = false;
150: long value = 0;
151: int i = start;
152: if (s.charAt(i) == '+') {
153: i++;
154: } else if (s.charAt(i) == '-') {
155: negative = true;
156: i++;
157: }
158: if (i > last) {
159: return numericError("Cannot convert string "
160: + Err.wrap(s, Err.VALUE)
161: + " to integer: no digits after the sign");
162: }
163: while (i <= last) {
164: char d = s.charAt(i++);
165: if (d >= '0' && d <= '9') {
166: value = 10 * value + (d - '0');
167: } else {
168: return numericError("Cannot convert string "
169: + Err.wrap(s, Err.VALUE) + " to an integer");
170: }
171: }
172: return new IntegerValue((negative ? -value : value));
173: } else {
174: // for longer numbers, rely on library routines
175: try {
176: CharSequence t = trimWhitespace(s);
177: if (t.charAt(0) == '+') {
178: t = t.subSequence(1, t.length());
179: }
180: if (t.length() < 16) {
181: return new IntegerValue(Long
182: .parseLong(t.toString()));
183: } else {
184: return new BigIntegerValue(new BigInteger(t
185: .toString()));
186: }
187: } catch (NumberFormatException err) {
188: return numericError("Cannot convert string "
189: + Err.wrap(s, Err.VALUE) + " to an integer");
190: }
191: }
192: }
193:
194: /**
195: * Helper method to handle errors converting a string to a number
196: * @param message error message
197: * @return an ErrorValue encapsulating an Exception describing the error
198: */
199: private static ValidationErrorValue numericError(String message) {
200: ValidationException err = new ValidationException(message);
201: err.setErrorCode("FORG0001");
202: return new ValidationErrorValue(err);
203: }
204:
205: /**
206: * Check that a value is in range for the specified subtype of xs:integer
207: *
208: * @param value the value to be checked
209: * @param type the required item type, a subtype of xs:integer
210: * @return true if successful, false if value is out of range for the subtype
211: */
212:
213: static boolean checkRange(long value, AtomicType type) {
214: for (int i = 0; i < ranges.length; i += 3) {
215: if (ranges[i] == type.getFingerprint()) {
216: long min = ranges[i + 1];
217: if (min != NO_LIMIT && value < min) {
218: return false;
219: }
220: long max = ranges[i + 2];
221: if (max != NO_LIMIT && max != MAX_UNSIGNED_LONG
222: && value > max) {
223: return false;
224: }
225: return true;
226: }
227: }
228: throw new IllegalArgumentException(
229: "No range information found for integer subtype "
230: + type.getDescription());
231: }
232:
233: /**
234: * Check that a BigInteger is within the required range for a given integer subtype.
235: * This method is expensive, so it should not be used unless the BigInteger is outside the range of a long.
236: */
237:
238: static boolean checkBigRange(BigInteger big, AtomicType type) {
239:
240: for (int i = 0; i < ranges.length; i += 3) {
241: if (ranges[i] == type.getFingerprint()) {
242: long min = ranges[i + 1];
243: if (min != NO_LIMIT
244: && BigInteger.valueOf(min).compareTo(big) > 0) {
245: return false;
246: }
247: long max = ranges[i + 2];
248: if (max == NO_LIMIT) {
249: return true;
250: } else if (max == MAX_UNSIGNED_LONG) {
251: return BigIntegerValue.MAX_UNSIGNED_LONG
252: .compareTo(big) >= 0;
253: } else {
254: return BigInteger.valueOf(max).compareTo(big) >= 0;
255: }
256: }
257: }
258: throw new IllegalArgumentException(
259: "No range information found for integer subtype "
260: + type.getDescription());
261: }
262:
263: /**
264: * Static data identifying the min and max values for each built-in subtype of xs:integer.
265: * This is a sequence of triples, each holding the fingerprint of the type, the minimum
266: * value, and the maximum value. The special value NO_LIMIT indicates that there is no
267: * minimum (or no maximum) for this type. The special value MAX_UNSIGNED_LONG represents the
268: * value 2^64-1
269: */
270: private static long NO_LIMIT = -9999;
271: private static long MAX_UNSIGNED_LONG = -9998;
272: private static long[] ranges = { Type.INTEGER, NO_LIMIT, NO_LIMIT,
273: Type.NON_POSITIVE_INTEGER, NO_LIMIT, 0,
274: Type.NEGATIVE_INTEGER, NO_LIMIT, -1, Type.LONG,
275: Long.MIN_VALUE, Long.MAX_VALUE, Type.INT,
276: Integer.MIN_VALUE, Integer.MAX_VALUE, Type.SHORT,
277: Short.MIN_VALUE, Short.MAX_VALUE, Type.BYTE,
278: Byte.MIN_VALUE, Byte.MAX_VALUE, Type.NON_NEGATIVE_INTEGER,
279: 0, NO_LIMIT, Type.POSITIVE_INTEGER, 1, NO_LIMIT,
280: Type.UNSIGNED_LONG, 0, MAX_UNSIGNED_LONG,
281: Type.UNSIGNED_INT, 0, 4294967295L, Type.UNSIGNED_SHORT, 0,
282: 65535, Type.UNSIGNED_BYTE, 0, 255 };
283:
284: /**
285: * Get the hashCode. This must conform to the rules for other NumericValue hashcodes
286: * @see NumericValue#hashCode
287: */
288:
289: public int hashCode() {
290: if (value > Integer.MIN_VALUE && value < Integer.MAX_VALUE) {
291: return (int) value;
292: } else {
293: return new Double(this .getDoubleValue()).hashCode();
294: }
295: }
296:
297: /**
298: * Get the value
299: * @return the value of the xs:integer, as a Java long
300: */
301:
302: public long longValue() {
303: return value;
304: }
305:
306: /**
307: * Return the effective boolean value of this integer
308: * @param context The dynamic evaluation context; ignored in this
309: * implementation of the method
310: * @return false if the integer is zero, otherwise true
311: */
312: public boolean effectiveBooleanValue(XPathContext context) {
313: return value != 0;
314: }
315:
316: /**
317: * Compare the value to another numeric value
318: * @param other the numeric value to be compared to this value
319: * @return -1 if this value is less than the other, 0 if they are equal,
320: * +1 if this value is greater
321: */
322:
323: public int compareTo(Object other) {
324: if (other instanceof IntegerValue) {
325: long val2 = ((IntegerValue) other).value;
326: if (value == val2)
327: return 0;
328: if (value < val2)
329: return -1;
330: return 1;
331: } else if (other instanceof BigIntegerValue) {
332: return new BigIntegerValue(value).compareTo(other);
333: } else {
334: return super .compareTo(other);
335: }
336: }
337:
338: /**
339: * Compare two values for equality. This supports identity constraints in XML Schema,
340: * which allow list-valued elements and attributes to participate in key and uniqueness constraints.
341: * This method returns false if any error occurs during the comparison, or if any of the items
342: * in either sequence is a node rather than an atomic value. The default implementation of
343: * schemaEquals() is the same as equals(), but subclasses can override this.
344: */
345:
346: public boolean schemaEquals(Value obj) {
347: if (obj instanceof AtomicValue) {
348: obj = ((AtomicValue) obj).getPrimitiveValue();
349: }
350: if (obj instanceof DecimalValue) {
351: return obj.schemaEquals(this );
352: } else if (obj instanceof IntegerValue) {
353: return value == ((IntegerValue) obj).value;
354: } else if (obj instanceof BigIntegerValue) {
355: return equals(obj);
356: } else {
357: return false;
358: }
359: }
360:
361: /**
362: * Convert to target data type
363: *
364: * @param requiredType an integer identifying the required atomic type
365: * @param context
366: * @return an AtomicValue, a value of the required type
367: */
368:
369: public AtomicValue convertPrimitive(BuiltInAtomicType requiredType,
370: boolean validate, XPathContext context) {
371: switch (requiredType.getFingerprint()) {
372: case Type.BOOLEAN:
373: return BooleanValue.get(value != 0);
374:
375: case Type.NUMBER:
376: case Type.INTEGER:
377: case Type.ANY_ATOMIC:
378: case Type.ITEM:
379: return this ;
380:
381: case Type.NON_POSITIVE_INTEGER:
382: case Type.NEGATIVE_INTEGER:
383: case Type.LONG:
384: case Type.INT:
385: case Type.SHORT:
386: case Type.BYTE:
387: case Type.NON_NEGATIVE_INTEGER:
388: case Type.POSITIVE_INTEGER:
389: case Type.UNSIGNED_LONG:
390: case Type.UNSIGNED_INT:
391: case Type.UNSIGNED_SHORT:
392: case Type.UNSIGNED_BYTE:
393: IntegerValue val = new IntegerValue(value);
394: ValidationException err = val.convertToSubtype(
395: requiredType, validate);
396: if (err != null) {
397: return new ValidationErrorValue(err);
398: }
399: return val;
400:
401: case Type.DOUBLE:
402: return new DoubleValue((double) value);
403:
404: case Type.FLOAT:
405: return new FloatValue((float) value);
406:
407: case Type.DECIMAL:
408: return new DecimalValue(value);
409:
410: case Type.STRING:
411: return new StringValue(getStringValue());
412:
413: case Type.UNTYPED_ATOMIC:
414: return new UntypedAtomicValue(getStringValue());
415:
416: default:
417: ValidationException err2 = new ValidationException(
418: "Cannot convert integer to "
419: + requiredType.getDisplayName());
420: err2.setErrorCode("XPTY0004");
421: return new ValidationErrorValue(err2);
422: }
423: }
424:
425: /**
426: * Get the value as a String
427: *
428: * @return a String representation of the value
429: */
430:
431: public String getStringValue() {
432: return value + "";
433: }
434:
435: /**
436: * Negate the value
437: *
438: * @return the result of inverting the sign of the value
439: */
440:
441: public NumericValue negate() {
442: return new IntegerValue(-value);
443: }
444:
445: /**
446: * Implement the XPath floor() function
447: * @return the integer value, unchanged
448: */
449:
450: public NumericValue floor() {
451: return this ;
452: }
453:
454: /**
455: * Implement the XPath ceiling() function
456: * @return the integer value, unchanged
457: */
458:
459: public NumericValue ceiling() {
460: return this ;
461: }
462:
463: /**
464: * Implement the XPath round() function
465: * @return the integer value, unchanged
466: */
467:
468: public NumericValue round() {
469: return this ;
470: }
471:
472: /**
473: * Implement the XPath round-to-half-even() function
474: *
475: * @param scale number of digits required after the decimal point; the
476: * value -2 (for example) means round to a multiple of 100
477: * @return if the scale is >=0, return this value unchanged. Otherwise
478: * round it to a multiple of 10**-scale
479: */
480:
481: public NumericValue roundToHalfEven(int scale) {
482: long absolute = Math.abs(value);
483: if (scale >= 0) {
484: return this ;
485: } else {
486: if (scale < -15) {
487: return new BigIntegerValue(value)
488: .roundToHalfEven(scale);
489: }
490: long factor = 1;
491: for (long i = 1; i <= -scale; i++) {
492: factor *= 10;
493: }
494: long modulus = absolute % factor;
495: long rval = absolute - modulus;
496: long d = modulus * 2;
497: if (d > factor) {
498: rval += factor;
499: } else if (d < factor) {
500: // no-op
501: } else {
502: // round to even
503: if (rval % (2 * factor) == 0) {
504: // no-op
505: } else {
506: rval += factor;
507: }
508: }
509: if (value < 0)
510: rval = -rval;
511: return new IntegerValue(rval);
512: }
513: }
514:
515: /**
516: * Determine whether the value is negative, zero, or positive
517: * @return -1 if negative, 0 if zero, +1 if positive, NaN if NaN
518: */
519:
520: public double signum() {
521: if (value > 0)
522: return +1;
523: if (value == 0)
524: return 0;
525: return -1;
526: }
527:
528: /**
529: * Determine whether the value is a whole number, that is, whether it compares
530: * equal to some integer
531: *
532: * @return always true for this implementation
533: */
534:
535: public boolean isWholeNumber() {
536: return true;
537: }
538:
539: /**
540: * Evaluate a binary arithmetic operator.
541: *
542: * @param operator the operator to be applied, identified by a constant in
543: * the Tokenizer class
544: * @param other the other operand of the arithmetic expression
545: * @exception XPathException if an arithmetic failure occurs, e.g. divide
546: * by zero
547: * @return the result of performing the arithmetic operation
548: */
549:
550: public NumericValue arithmetic(int operator, NumericValue other,
551: XPathContext context) throws XPathException {
552:
553: if (other instanceof IntegerValue) {
554: // if either of the values is large, we use BigInteger arithmetic to be on the safe side
555: if (value >= Integer.MAX_VALUE
556: || value <= Integer.MIN_VALUE
557: || ((IntegerValue) other).value >= Integer.MAX_VALUE
558: || ((IntegerValue) other).value <= Integer.MIN_VALUE) {
559: return new BigIntegerValue(value).arithmetic(operator,
560: other, context);
561: }
562: switch (operator) {
563: case Token.PLUS:
564: return new IntegerValue(value
565: + ((IntegerValue) other).value);
566: case Token.MINUS:
567: return new IntegerValue(value
568: - ((IntegerValue) other).value);
569: case Token.MULT:
570: return new IntegerValue(value
571: * ((IntegerValue) other).value);
572: case Token.IDIV:
573: try {
574: return new IntegerValue(value
575: / ((IntegerValue) other).value);
576: } catch (ArithmeticException err) {
577: DynamicError e;
578: if ("/ by zero".equals(err.getMessage())) {
579: e = new DynamicError("Integer division by zero");
580: e.setErrorCode("FOAR0001");
581: } else {
582: e = new DynamicError(
583: "Integer division failure", err);
584: }
585: e.setXPathContext(context);
586: throw e;
587: }
588: case Token.DIV:
589: // the result of dividing two integers is a decimal; but if
590: // one divides exactly by the other, we implement it as an integer
591: long quotient = ((IntegerValue) other).value;
592: if (quotient == 0) {
593: DynamicError err = new DynamicError(
594: "Integer division by zero");
595: err.setXPathContext(context);
596: err.setErrorCode("FOAR0001");
597: throw err;
598: }
599: if (value % quotient == 0) {
600: return new IntegerValue(value / quotient);
601: }
602: return new DecimalValue(value).arithmetic(Token.DIV,
603: new DecimalValue(quotient), context);
604: case Token.MOD:
605: return new IntegerValue(value
606: % ((IntegerValue) other).value);
607: default:
608: throw new UnsupportedOperationException(
609: "Unknown operator");
610: }
611: } else if (other instanceof BigIntegerValue) {
612: return new BigIntegerValue(value).arithmetic(operator,
613: other, context);
614: } else {
615: NumericValue v = (NumericValue) convert(other.getItemType(
616: null).getPrimitiveType(), context);
617: return v.arithmetic(operator, other, context);
618: }
619: }
620:
621: /**
622: * Determine the data type of the expression
623: * @return the actual data type
624: * @param th
625: */
626:
627: public ItemType getItemType(TypeHierarchy th) {
628: return type;
629: }
630:
631: /**
632: * Get conversion preference for this value to a Java class.
633: *
634: * @param required the Java class to which conversion is required
635: * @return the conversion preference. A low result indicates higher
636: * preference.
637: */
638:
639: // Note: this table gives java Long preference over Integer, even if the
640: // XML Schema type is xs:int
641: /**
642: * Convert to Java object (for passing to external functions)
643: *
644: * @param target The Java class to which conversion is required
645: * @exception XPathException if conversion is not possible, or fails
646: * @return the Java object that results from the conversion; always an
647: * instance of the target class
648: */
649:
650: public Object convertToJava(Class target, XPathContext context)
651: throws XPathException {
652: if (target == Object.class) {
653: return new Long(value);
654: } else if (target.isAssignableFrom(IntegerValue.class)) {
655: return this ;
656: } else if (target == boolean.class) {
657: BooleanValue bval = (BooleanValue) convert(Type.BOOLEAN,
658: context);
659: return Boolean.valueOf(bval.getBooleanValue());
660: } else if (target == Boolean.class) {
661: BooleanValue bval = (BooleanValue) convert(Type.BOOLEAN,
662: context);
663: return Boolean.valueOf(bval.getBooleanValue());
664: } else if (target == String.class
665: || target == CharSequence.class) {
666: return getStringValue();
667: } else if (target == double.class || target == Double.class) {
668: return new Double(value);
669: } else if (target == float.class || target == Float.class) {
670: return new Float(value);
671: } else if (target == long.class || target == Long.class) {
672: return new Long(value);
673: } else if (target == int.class || target == Integer.class) {
674: return new Integer((int) value);
675: } else if (target == short.class || target == Short.class) {
676: return new Short((short) value);
677: } else if (target == byte.class || target == Byte.class) {
678: return new Byte((byte) value);
679: } else if (target == char.class || target == Character.class) {
680: return new Character((char) value);
681: } else if (target == BigInteger.class) {
682: return BigInteger.valueOf(value);
683: } else if (target == BigDecimal.class) {
684: return BigDecimal.valueOf(value);
685: } else {
686: Object o = super .convertToJava(target, context);
687: if (o == null) {
688: throw new DynamicError("Conversion of integer to "
689: + target.getName() + " is not supported");
690: }
691: return o;
692: }
693: }
694:
695: }
696:
697: //
698: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
699: // you may not use this file except in compliance with the License. You may obtain a copy of the
700: // License at http://www.mozilla.org/MPL/
701: //
702: // Software distributed under the License is distributed on an "AS IS" basis,
703: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
704: // See the License for the specific language governing rights and limitations under the License.
705: //
706: // The Original Code is: all this file except the asStringXT() and zeros() methods (not currently used).
707: //
708: // The Initial Developer of the Original Code is Michael H. Kay.
709: //
710: // Portions created by (xt) are Copyright (C) (James Clark). All Rights Reserved.
711: //
712: // Contributor(s): none.
713: //
|