KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > saxon > value > DecimalValue


1 package net.sf.saxon.value;
2 import net.sf.saxon.Err;
3 import net.sf.saxon.ConversionContext;
4 import net.sf.saxon.om.FastStringBuffer;
5 import net.sf.saxon.expr.Token;
6 import net.sf.saxon.expr.XPathContext;
7 import net.sf.saxon.trans.DynamicError;
8 import net.sf.saxon.trans.XPathException;
9 import net.sf.saxon.type.BuiltInAtomicType;
10 import net.sf.saxon.type.ItemType;
11 import net.sf.saxon.type.Type;
12 import net.sf.saxon.type.ValidationException;
13
14 import java.math.BigDecimal JavaDoc;
15 import java.math.BigInteger JavaDoc;
16 import java.util.regex.Pattern JavaDoc;
17
18 /**
19 * A decimal value
20 */

21
22 public final class DecimalValue extends NumericValue {
23
24     private static final int DIVIDE_PRECISION = 18;
25
26     private BigDecimal JavaDoc value;
27
28     /**
29     * Constructor supplying a BigDecimal
30     * @param value the value of the DecimalValue
31     */

32
33     public DecimalValue(BigDecimal JavaDoc value) {
34         this.value = value;
35         loseTrailingZeros();
36     }
37
38     private static final Pattern JavaDoc decimalPattern = Pattern.compile("(\\-|\\+)?((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))");
39
40     /**
41     * Factory method to construct a DecimalValue from a string
42     * @param in the value of the DecimalValue
43      * @param validate true if validation is required; false if the caller knows that the value is valid
44      * @return the required DecimalValue if the input is valid, or an ErrorValue encapsulating the error
45      * message if not.
46      */

47
48     public static AtomicValue makeDecimalValue(CharSequence JavaDoc in, boolean validate) {
49         String JavaDoc trimmed = trimWhitespace(in).toString();
50         try {
51             if (validate) {
52                 if (!decimalPattern.matcher(trimmed).matches()) {
53                     ValidationException err = new ValidationException(
54                             "Cannot convert string " + Err.wrap(trimmed, Err.VALUE) + " to xs:decimal");
55                     err.setErrorCode("FORG0001");
56                     return new ValidationErrorValue(err);
57                 }
58             }
59             BigDecimal JavaDoc val = new BigDecimal JavaDoc(trimmed);
60             DecimalValue value = new DecimalValue(val);
61             if (trimmed.charAt(trimmed.length()-1) == '0') {
62                 value.loseTrailingZeros();
63             }
64             return value;
65         } catch (NumberFormatException JavaDoc err) {
66             ValidationException e = new ValidationException(
67                     "Cannot convert string " + Err.wrap(trimmed, Err.VALUE) + " to xs:decimal");
68             e.setErrorCode("FORG0001");
69             return new ValidationErrorValue(e);
70         }
71     }
72
73     /**
74     * Constructor supplying a double
75     * @param in the value of the DecimalValue
76     */

77
78     public DecimalValue(double in) throws ValidationException {
79         try {
80             this.value = new BigDecimal JavaDoc(in);
81             loseTrailingZeros();
82         } catch (NumberFormatException JavaDoc err) {
83             // Must be a special value such as NaN or infinity
84
ValidationException e = new ValidationException(
85                     "Cannot convert double " + Err.wrap(in+"", Err.VALUE) + " to decimal");
86             e.setErrorCode("FORG0001");
87             throw e;
88         }
89     }
90
91     /**
92     * Constructor supplying a long integer
93     * @param in the value of the DecimalValue
94     */

95
96     public DecimalValue(long in) {
97         this.value = BigDecimal.valueOf(in);
98     }
99
100     /**
101     * Remove insignificant trailing zeros (the Java BigDecimal class retains trailing zeros,
102     * but the XPath 2.0 xs:decimal type does not)
103     */

104
105     private void loseTrailingZeros() {
106         int scale = value.scale();
107         if (scale > 0) {
108             BigInteger JavaDoc i = value.unscaledValue();
109             while (true) {
110                 BigInteger JavaDoc[] dr = i.divideAndRemainder(TEN);
111                 if (dr[1].equals(BigInteger.ZERO)) {
112                     i = dr[0];
113                     scale--;
114                     if (scale==0) {
115                         break;
116                     }
117                 } else {
118                     break;
119                 }
120             }
121             if (scale != value.scale()) {
122                 value = new BigDecimal JavaDoc(i, scale);
123             }
124         }
125     }
126
127     private static final BigInteger JavaDoc TEN = BigInteger.valueOf(10);
128
129     /**
130     * Get the value
131     */

132
133     public BigDecimal JavaDoc getValue() {
134         return value;
135     }
136
137     /**
138      * Get the hashCode. This must conform to the rules for other NumericValue hashcodes
139      * @see NumericValue#hashCode
140      */

141
142     public int hashCode() {
143         BigDecimal JavaDoc round = value.setScale(0, BigDecimal.ROUND_DOWN);
144         long value = round.longValue();
145         if (value > Integer.MIN_VALUE && value < Integer.MAX_VALUE) {
146             return (int)value;
147         } else {
148             return new Double JavaDoc(this.getDoubleValue()).hashCode();
149         }
150     }
151
152     public boolean effectiveBooleanValue(XPathContext context) {
153         return value.signum() != 0;
154     }
155
156     /**
157     * Convert to target data type
158     */

159
160     public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) {
161         switch(requiredType.getPrimitiveType()) {
162         case Type.BOOLEAN:
163                 // 0.0 => false, anything else => true
164
return BooleanValue.get(value.signum()!=0);
165         case Type.NUMBER:
166         case Type.DECIMAL:
167         case Type.ATOMIC:
168         case Type.ITEM:
169             return this;
170         case Type.INTEGER:
171             return BigIntegerValue.makeValue(value.toBigInteger());
172         case Type.DOUBLE:
173             return new DoubleValue(value.doubleValue());
174         case Type.FLOAT:
175             return new FloatValue(value.floatValue());
176         case Type.STRING:
177             return new StringValue(getStringValueCS());
178         case Type.UNTYPED_ATOMIC:
179             return new UntypedAtomicValue(getStringValueCS());
180         default:
181             ValidationException err = new ValidationException("Cannot convert decimal to " +
182                                      requiredType.getDisplayName());
183             //err.setXPathContext(context);
184
err.setErrorCode("FORG0001");
185             return new ValidationErrorValue(err);
186         }
187     }
188
189     /**
190     * Get the value as a String
191     * @return a String representation of the value
192     */

193
194     public String JavaDoc getStringValue() {
195         // Can't use the plain BigDecimal#toString() under JDK 1.5 because this produces values like "1E-5".
196
// TODO: JDK 1.5 offers BigDecimal#toPlainString() which might do the job directly
197
if (value.scale() <= 0) {
198             return value.toString();
199         } else {
200             boolean negative = value.signum() < 0;
201             String JavaDoc s = value.abs().unscaledValue().toString();
202             int len = s.length();
203             int scale = value.scale();
204             FastStringBuffer sb = new FastStringBuffer(len+1);
205             if (negative) {
206                 sb.append('-');
207             }
208             if (scale >= len) {
209                 sb.append("0.");
210                 for (int i=len; i<scale; i++) {
211                     sb.append('0');
212                 }
213                 sb.append(s);
214             } else {
215                 sb.append(s.substring(0, len-scale));
216                 sb.append('.');
217                 sb.append(s.substring(len-scale));
218             }
219             return sb.toString();
220         }
221     }
222
223     /**
224     * Determine the data type of the expression
225     * @return Type.DECIMAL
226     */

227
228     public ItemType getItemType() {
229         return Type.DECIMAL_TYPE;
230     }
231
232     /**
233     * Negate the value
234     */

235
236     public NumericValue negate() {
237         return new DecimalValue(value.negate());
238     }
239
240     /**
241     * Implement the XPath floor() function
242     */

243
244     public NumericValue floor() {
245         return new DecimalValue(value.setScale(0, BigDecimal.ROUND_FLOOR));
246     }
247
248     /**
249     * Implement the XPath ceiling() function
250     */

251
252     public NumericValue ceiling() {
253         return new DecimalValue(value.setScale(0, BigDecimal.ROUND_CEILING));
254     }
255
256     /**
257     * Implement the XPath round() function
258     */

259
260     public NumericValue round() {
261         // The XPath rules say that we should round to the nearest integer, with .5 rounding towards
262
// positive infinity. Unfortunately this is not one of the rounding modes that the Java BigDecimal
263
// class supports.
264

265         // If the value is positive, we use ROUND_HALF_UP; if it is negative, we use ROUND_HALF_DOWN (here "UP"
266
// means "away from zero")
267

268         switch (value.signum()) {
269             case -1:
270                 return new DecimalValue(value.setScale(0, BigDecimal.ROUND_HALF_DOWN));
271             case 0:
272                 return this;
273             case +1:
274                 return new DecimalValue(value.setScale(0, BigDecimal.ROUND_HALF_UP));
275             default:
276                 // can't happen
277
return this;
278         }
279
280     }
281
282     /**
283     * Implement the XPath round-to-half-even() function
284     */

285
286     public NumericValue roundToHalfEven(int scale) {
287         if (scale<0) {
288             try {
289                 AtomicValue val = convert(Type.INTEGER, null);
290                 if (val instanceof IntegerValue) {
291                     return ((IntegerValue)val).roundToHalfEven(scale);
292                 } else {
293                     return ((BigIntegerValue)val).roundToHalfEven(scale);
294                 }
295             } catch (XPathException err) {
296                 throw new IllegalArgumentException JavaDoc("internal error in integer-decimal conversion");
297             }
298         } else {
299             return new DecimalValue(value.setScale(scale, BigDecimal.ROUND_HALF_EVEN));
300         }
301     }
302
303     /**
304      * Determine whether the value is negative, zero, or positive
305      * @return -1 if negative, 0 if zero, +1 if positive, NaN if NaN
306      */

307
308     public double signum() {
309         return value.signum();
310     }
311
312     /**
313     * Determine whether the value is a whole number, that is, whether it compares
314     * equal to some integer
315     */

316
317     public boolean isWholeNumber() {
318         return value.scale()==0 ||
319                value.equals(value.setScale(0, BigDecimal.ROUND_DOWN));
320     }
321
322     /**
323     * Evaluate a binary arithmetic operator.
324     */

325
326     public NumericValue arithmetic(int operator, NumericValue other, XPathContext context) throws XPathException {
327         if (other instanceof DecimalValue) {
328             try {
329                 switch(operator) {
330                     case Token.PLUS:
331                         return new DecimalValue(value.add(((DecimalValue)other).value));
332                     case Token.MINUS:
333                         return new DecimalValue(value.subtract(((DecimalValue)other).value));
334                     case Token.MULT:
335                         return new DecimalValue(value.multiply(((DecimalValue)other).value));
336                     case Token.DIV:
337                         int scale = Math.max(DIVIDE_PRECISION,
338                                              Math.max(value.scale(), ((DecimalValue)other).value.scale()));
339                         //int scale = value.scale() + ((DecimalValue)other).value.scale() + DIVIDE_PRECISION;
340
BigDecimal JavaDoc result = value.divide(((DecimalValue)other).value, scale, BigDecimal.ROUND_HALF_DOWN);
341                         return new DecimalValue(result);
342                     case Token.IDIV:
343                         if (((DecimalValue)other).value.signum() == 0) {
344                             DynamicError e = new DynamicError("Integer division by zero");
345                             e.setErrorCode("FOAR0001");
346                             e.setXPathContext(context);
347                             throw e;
348                         }
349                         BigInteger JavaDoc quot = value.divide(((DecimalValue)other).value, 0, BigDecimal.ROUND_DOWN).toBigInteger();
350                         return BigIntegerValue.makeValue(quot);
351                     case Token.MOD:
352                         //BigDecimal quotient = value.divide(((DecimalValue)other).value, ((DecimalValue)other).value.scale(), BigDecimal.ROUND_DOWN);
353
BigDecimal JavaDoc quotient = value.divide(((DecimalValue)other).value, 0, BigDecimal.ROUND_DOWN);
354                         BigDecimal JavaDoc remainder = value.subtract(quotient.multiply(((DecimalValue)other).value));
355                         return new DecimalValue(remainder);
356                     default:
357                         throw new AssertionError JavaDoc("Unknown operator");
358                 }
359             } catch (ArithmeticException JavaDoc err) {
360                 throw new DynamicError(err);
361             }
362         } else if (NumericValue.isInteger(other)) {
363             return arithmetic(operator, (DecimalValue)other.convert(Type.DECIMAL, context), context);
364         } else {
365             NumericValue n = (NumericValue)convert(other.getItemType().getPrimitiveType(), context);
366             return n.arithmetic(operator, other, context);
367         }
368     }
369
370     /**
371     * Compare the value to another numeric value
372     */

373
374     public int compareTo(Object JavaDoc other) {
375         if ((NumericValue.isInteger((NumericValue)other))) {
376             // deliberately triggers a ClassCastException if other value is the wrong type
377
try {
378                 return compareTo(((NumericValue)other).convert(Type.DECIMAL, null));
379             } catch (XPathException err) {
380                 throw new AssertionError JavaDoc("Conversion of integer to decimal should never fail");
381             }
382         } else if (other instanceof BigIntegerValue) {
383             return value.compareTo(((BigIntegerValue)other).asDecimal());
384         } else if (other instanceof DecimalValue) {
385             return value.compareTo(((DecimalValue)other).value);
386         } else {
387             return super.compareTo(other);
388         }
389     }
390
391     /**
392      * Compare two values for equality. This supports identity constraints in XML Schema,
393      * which allow list-valued elements and attributes to participate in key and uniqueness constraints.
394      * This method returns false if any error occurs during the comparison, or if any of the items
395      * in either sequence is a node rather than an atomic value. The default implementation of
396      * schemaEquals() is the same as equals(), but subclasses can override this.
397      */

398
399     public boolean schemaEquals(Value obj) {
400         if (obj instanceof AtomicValue) {
401             obj = ((AtomicValue)obj).getPrimitiveValue();
402         }
403         try {
404             if (obj instanceof DecimalValue) {
405                 return value.equals(((DecimalValue)obj).value);
406             } else if (obj instanceof IntegerValue) {
407                 return schemaEquals(((IntegerValue)obj).convert(Type.DECIMAL, null));
408             } else if (obj instanceof BigIntegerValue) {
409                 return schemaEquals(((BigIntegerValue)obj).convert(Type.DECIMAL, null));
410             } else {
411                 return false;
412             }
413         } catch (XPathException e) {
414             return false;
415         }
416     }
417
418     /**
419     * Convert to Java object (for passing to external functions)
420     */

421
422     public Object JavaDoc convertToJava(Class JavaDoc target, XPathContext context) throws XPathException {
423         if (target==Object JavaDoc.class || target.isAssignableFrom(BigDecimal JavaDoc.class)) {
424             return value;
425         } else if (target.isAssignableFrom(DecimalValue.class)) {
426             return this;
427         } else if (target==boolean.class) {
428             BooleanValue bval = (BooleanValue)convert(Type.BOOLEAN, context);
429             return Boolean.valueOf(bval.getBooleanValue());
430         } else if (target==Boolean JavaDoc.class) {
431             BooleanValue bval = (BooleanValue)convert(Type.BOOLEAN, context);
432             return Boolean.valueOf(bval.getBooleanValue());
433         } else if (target==String JavaDoc.class || target==CharSequence JavaDoc.class) {
434             return getStringValue();
435         } else if (target==double.class || target==Double JavaDoc.class) {
436             return new Double JavaDoc(value.doubleValue());
437         } else if (target==float.class || target==Float JavaDoc.class) {
438             return new Float JavaDoc(value.floatValue());
439         } else if (target==long.class || target==Long JavaDoc.class) {
440             return new Long JavaDoc(value.longValue());
441         } else if (target==int.class || target==Integer JavaDoc.class) {
442             return new Integer JavaDoc(value.intValue());
443         } else if (target==short.class || target==Short JavaDoc.class) {
444             return new Short JavaDoc(value.shortValue());
445         } else if (target==byte.class || target==Byte JavaDoc.class) {
446             return new Byte JavaDoc(value.byteValue());
447         } else if (target==char.class || target==Character JavaDoc.class) {
448             return new Character JavaDoc((char)value.intValue());
449         } else {
450             Object JavaDoc o = super.convertToJava(target, context);
451             if (o == null) {
452                 throw new DynamicError("Conversion of decimal to " + target.getName() +
453                         " is not supported");
454             }
455             return o;
456         }
457     }
458
459
460 }
461
462 //
463
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
464
// you may not use this file except in compliance with the License. You may obtain a copy of the
465
// License at http://www.mozilla.org/MPL/
466
//
467
// Software distributed under the License is distributed on an "AS IS" basis,
468
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
469
// See the License for the specific language governing rights and limitations under the License.
470
//
471
// The Original Code is: all this file except the asStringXT() and zeros() methods (not currently used).
472
//
473
// The Initial Developer of the Original Code is Michael H. Kay.
474
//
475
// Portions created by (xt) are Copyright (C) (James Clark). All Rights Reserved.
476
//
477
// Contributor(s): none.
478
//
479

480
Popular Tags