KickJava   Java API By Example, From Geeks To Geeks.

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


1 package net.sf.saxon.value;
2 import net.sf.saxon.expr.XPathContext;
3 import net.sf.saxon.functions.Component;
4 import net.sf.saxon.om.FastStringBuffer;
5 import net.sf.saxon.trans.DynamicError;
6 import net.sf.saxon.trans.XPathException;
7 import net.sf.saxon.type.BuiltInAtomicType;
8 import net.sf.saxon.type.ItemType;
9 import net.sf.saxon.type.Type;
10 import net.sf.saxon.type.ValidationException;
11 import net.sf.saxon.ConversionContext;
12
13 import java.util.StringTokenizer JavaDoc;
14
15 /**
16 * A value of type xs:duration
17 */

18
19 public class DurationValue extends AtomicValue {
20
21     protected boolean negative = false;
22     protected int years = 0;
23     protected int months = 0;
24     protected int days = 0;
25     protected int hours = 0;
26     protected int minutes = 0;
27     protected int seconds = 0;
28     protected int microseconds = 0;
29     private boolean normalized = false;
30
31     /**
32     * Private constructor for internal use
33     */

34
35     protected DurationValue() {
36     }
37
38     public DurationValue(boolean positive, int years, int months, int days,
39                          int hours, int minutes, int seconds, int microseconds) {
40         this.negative = !positive;
41         this.years = years;
42         this.months = months;
43         this.days = days;
44         this.hours = hours;
45         this.minutes = minutes;
46         this.seconds = seconds;
47         this.microseconds = microseconds;
48         normalized = (months<12 && hours<24 && minutes<60 && seconds<60 && microseconds<1000000);
49     }
50
51     /**
52     * Constructor: create a duration value from a supplied string, in
53     * ISO 8601 format [+|-]PnYnMnDTnHnMnS
54     */

55
56     public DurationValue(CharSequence JavaDoc s) throws XPathException {
57         StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(trimWhitespace(s).toString(), "-+.PYMDTHS", true);
58         try {
59             if (!tok.hasMoreElements()) badDuration("empty string", s);
60             String JavaDoc part = (String JavaDoc)tok.nextElement();
61             if ("+".equals(part)) {
62                 part = (String JavaDoc)tok.nextElement();
63             } else if ("-".equals(part)) {
64                 negative = true;
65                 part = (String JavaDoc)tok.nextElement();
66             }
67             if (!"P".equals(part)) badDuration("missing 'P'", s);
68             int state = 0;
69             while (tok.hasMoreElements()) {
70                 part = (String JavaDoc)tok.nextElement();
71                 if ("T".equals(part)) {
72                     state = 4;
73                     part = (String JavaDoc)tok.nextElement();
74                 }
75                 int value = Integer.parseInt(part);
76                 if (!tok.hasMoreElements()) badDuration("missing unit letter at end", s);
77                 char delim = ((String JavaDoc)tok.nextElement()).charAt(0);
78                 switch (delim) {
79                     case 'Y':
80                         if (state > 0) badDuration("Y is out of sequence", s);
81                         years = value;
82                         state = 1;
83                         break;
84                     case 'M':
85                         if (state == 4 || state==5) {
86                             minutes = value;
87                             state = 6;
88                             break;
89                         } else if (state == 0 || state==1) {
90                             months = value;
91                             state = 2;
92                             break;
93                         } else {
94                             badDuration("M is out of sequence", s);
95                         }
96                     case 'D':
97                         if (state > 2) badDuration("D is out of sequence", s);
98                         days = value;
99                         state = 3;
100                         break;
101                     case 'H':
102                         if (state != 4) badDuration("H is out of sequence", s);
103                         hours = value;
104                         state = 5;
105                         break;
106                     case '.':
107                         if (state < 4 || state > 6) badDuration("misplaced decimal point", s);
108                         seconds = value;
109                         state = 7;
110                         break;
111                     case 'S':
112                         if (state < 4 || state > 7) badDuration("S is out of sequence", s);
113                         if (state==7) {
114                             while (part.length() < 6) part += "0";
115                             if (part.length() > 6) part = part.substring(0, 6);
116                             microseconds = Integer.parseInt(part);
117                         } else {
118                             seconds = value;
119                         }
120                         state = 8;
121                         break;
122                    default:
123                         badDuration("misplaced " + delim, s);
124                 }
125             }
126             // Note, duration values (unlike the two xdt: subtypes) are not normalized
127

128         } catch (NumberFormatException JavaDoc err) {
129             badDuration("non-numeric component", s);
130         }
131     }
132
133     protected void badDuration(String JavaDoc msg, CharSequence JavaDoc s) throws XPathException {
134         DynamicError err = new DynamicError("Invalid duration value '" + s + "' (" + msg + ')');
135         err.setErrorCode("FORG0001");
136         throw err;
137     }
138
139     /**
140     * Convert to target data type
141     * @param requiredType an integer identifying the required atomic type
142     * @param conversion
143      * @return an AtomicValue, a value of the required type; or an ErrorValue
144     */

145
146     public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) {
147         //System.err.println("Convert duration " + getClass() + " to " + Type.getTypeName(requiredType));
148
switch(requiredType.getPrimitiveType()) {
149         case Type.DURATION:
150         case Type.ATOMIC:
151         case Type.ITEM:
152             return this;
153         case Type.STRING:
154             return new StringValue(getStringValueCS());
155         case Type.UNTYPED_ATOMIC:
156             return new UntypedAtomicValue(getStringValueCS());
157         case Type.YEAR_MONTH_DURATION:
158 // if (days!=0 || hours!=0 || minutes!=0 || seconds!=0 || microseconds!=0) {
159
// ValidationException err = new ValidationException(
160
// "Cannot convert to yearMonthDuration because some components are non-zero");
161
// //err.setXPathContext(context);
162
// err.setErrorCode("FORG0001");
163
// return new ValidationErrorValue(err);
164
// } else {
165
return MonthDurationValue.fromMonths((years*12 + months) * (negative ? -1 : +1));
166 // }
167
case Type.DAY_TIME_DURATION:
168 // if (years!=0 || months!=0) {
169
// ValidationException err = new ValidationException(
170
// "Cannot convert to dayTimeDuration because some components are non-zero");
171
// //err.setXPathContext(context);
172
// err.setErrorCode("FORG0001");
173
// return new ValidationErrorValue(err);
174
// } else {
175
return new SecondsDurationValue((negative?-1:+1), days, hours, minutes, seconds, microseconds);
176 // }
177
default:
178             ValidationException err = new ValidationException("Cannot convert duration to " +
179                                      requiredType.getDisplayName());
180             //err.setXPathContext(context);
181
err.setErrorCode("FORG0001");
182             return new ValidationErrorValue(err);
183         }
184     }
185
186     /**
187      * Normalize the duration, so that months<12, hours<24, minutes<60, seconds<60.
188      * At present we do this when converting to a string. It's possible that it should be done immediately
189      * on constructing the duration (so that component extraction functions get the normalized value).
190      * We're awaiting clarification of the spec...
191      */

192
193     public DurationValue normalizeDuration() {
194         int totalMonths = years*12 + months;
195         int years = totalMonths / 12;
196         int months = totalMonths % 12;
197         long totalMicroSeconds = ((((((days*24L + hours)*60L)+minutes)*60L)+seconds)*1000000L)+microseconds;
198         int microseconds = (int)(totalMicroSeconds % 1000000L);
199         int totalSeconds = (int)(totalMicroSeconds / 1000000L);
200         int seconds = totalSeconds % 60;
201         int totalMinutes = totalSeconds / 60;
202         int minutes = totalMinutes % 60;
203         int totalHours = totalMinutes / 60;
204         int hours = totalHours % 24;
205         int days = totalHours / 24;
206         return new DurationValue(!negative, years, months, days, hours, minutes, seconds, microseconds);
207
208     }
209
210
211     /**
212      * Convert the value to a string, using the serialization rules.
213      * For atomic values this is the same as a cast; for sequence values
214      * it gives a space-separated list. This method is refined for AtomicValues
215      * so that it never throws an Exception.
216      */

217
218     public String JavaDoc getStringValue() {
219         return getStringValueCS().toString();
220     }
221
222     /**
223     * Convert to string
224     * @return ISO 8601 representation.
225     */

226
227     public CharSequence JavaDoc getStringValueCS() {
228
229         // Note, Schema does not define a canonical representation. We omit all zero components, unless
230
// the duration is zero-length, in which case we output PT0S.
231

232         if (years==0 && months==0 && days==0 && hours==0 && minutes==0 && seconds==0 && microseconds==0) {
233             return "PT0S";
234         }
235
236         if (!normalized) {
237             return normalizeDuration().getStringValueCS();
238         }
239
240         FastStringBuffer sb = new FastStringBuffer(32);
241         if (negative) {
242             sb.append('-');
243         }
244         sb.append("P");
245         if (years != 0) {
246             sb.append(years + "Y");
247         }
248         if (months != 0) {
249             sb.append(months + "M");
250         }
251         if (days != 0) {
252             sb.append(days + "D");
253         }
254         if (hours != 0 || minutes != 0 || seconds != 0 || microseconds != 0) {
255             sb.append("T");
256         }
257         if (hours != 0) {
258             sb.append(hours + "H");
259         }
260         if (minutes != 0) {
261             sb.append(minutes + "M");
262         }
263         if (seconds != 0 || microseconds != 0) {
264             if (seconds != 0 && microseconds == 0) {
265                 sb.append(seconds + "S");
266             } else {
267                 long ms = (seconds * 1000000) + microseconds;
268                 String JavaDoc mss = ms + "";
269                 if (seconds == 0) {
270                     mss = "0000000" + mss;
271                     mss = mss.substring(mss.length()-7);
272                 }
273                 sb.append(mss.substring(0, mss.length()-6));
274                 sb.append('.');
275                 int lastSigDigit = mss.length()-1;
276                 while (mss.charAt(lastSigDigit) == '0') {
277                     lastSigDigit--;
278                 }
279                 sb.append(mss.substring(mss.length()-6, lastSigDigit+1));
280                 sb.append('S');
281             }
282         }
283
284         return sb;
285
286     }
287
288     /**
289     * Get length of duration in seconds, assuming an average length of month. (Note, this defines a total
290     * ordering on durations which is different from the partial order defined in XML Schema; XPath 2.0
291     * currently avoids defining an ordering at all. But the ordering here is consistent with the ordering
292     * of the two duration subtypes in XPath 2.0.)
293     */

294
295     public double getLengthInSeconds() {
296         double a = years;
297         a = a*12 + months;
298         a = a*(365.242199/12.0) + days;
299         a = a*24 + hours;
300         a = a*60 + minutes;
301         a = a*60 + seconds;
302         a = a + ((double)microseconds / 1000000);
303         return (negative ? -a : a);
304     }
305
306     /**
307     * Determine the data type of the exprssion
308     * @return Type.DURATION,
309     */

310
311     public ItemType getItemType() {
312         return Type.DURATION_TYPE;
313     }
314
315     /**
316     * Convert to Java object (for passing to external functions)
317     */

318
319     public Object JavaDoc convertToJava(Class JavaDoc target, XPathContext context) throws XPathException {
320         if (target.isAssignableFrom(DurationValue.class)) {
321             return this;
322         } else if (target==String JavaDoc.class || target==CharSequence JavaDoc.class) {
323             return getStringValue();
324         } else if (target==Object JavaDoc.class) {
325             return getStringValue();
326         } else {
327             Object JavaDoc o = super.convertToJava(target, context);
328             if (o == null) {
329                 DynamicError err = new DynamicError("Conversion of duration to " + target.getName() +
330                         " is not supported");
331                 err.setXPathContext(context);
332                 err.setErrorCode("SAXON:0000");
333             }
334             return o;
335         }
336     }
337
338     /**
339     * Get a component of the value
340     */

341
342     public AtomicValue getComponent(int component) throws XPathException {
343         // We define the method at this level, but the function signatures for the component
344
// extraction functions ensure that the method is only ever called on the two subtypes
345
switch (component) {
346         case Component.YEAR:
347             return new IntegerValue((negative?-years:years));
348         case Component.MONTH:
349             return new IntegerValue((negative?-months:months));
350         case Component.DAY:
351             return new IntegerValue((negative?-days:days));
352         case Component.HOURS:
353             return new IntegerValue((negative?-hours:hours));
354         case Component.MINUTES:
355             return new IntegerValue((negative?-minutes:minutes));
356         case Component.SECONDS:
357             FastStringBuffer sb = new FastStringBuffer(16);
358             String JavaDoc ms = ("000000" + microseconds);
359             ms = ms.substring(ms.length()-6);
360             sb.append((negative?"-":"") + seconds + '.' + ms);
361             return DecimalValue.makeDecimalValue(sb, false);
362         default:
363             throw new IllegalArgumentException JavaDoc("Unknown component for duration: " + component);
364         }
365     }
366
367
368     /**
369     * Compare the value to another duration value
370     * @param other The other dateTime value
371     * @return negative value if this one is the shorter duration, 0 if they are equal,
372     * positive value if this one is the longer duration. For this purpose, a year is considered
373     * to be equal to 365.242199 days.
374     * @throws ClassCastException if the other value is not a DurationValue (the parameter
375     * is declared as Object to satisfy the Comparable interface)
376     */

377
378 // public int compareTo(Object other) {
379
// if (!(other instanceof DurationValue)) {
380
// throw new ClassCastException("Duration values are not comparable to " + other.getClass());
381
// }
382
// double s1 = this.getLengthInSeconds();
383
// double s2 = ((DurationValue)other).getLengthInSeconds();
384
// if (s1==s2) return 0;
385
// if (s1<s2) return -1;
386
// return +1;
387
// }
388

389     /**
390     * Test if the two durations are of equal length. Note: this function is defined
391     * in XPath 2.0, but its semantics are currently unclear.
392     */

393
394     public boolean equals(Object JavaDoc other) {
395         if (!(other instanceof DurationValue)) {
396             throw new ClassCastException JavaDoc("Duration values are not comparable to " + other.getClass());
397         }
398         DurationValue d1 = normalizeDuration();
399         DurationValue d2 = ((DurationValue)other).normalizeDuration();
400         return d1.negative == d2.negative &&
401                 d1.years == d2.years &&
402                 d1.months == d2.months &&
403                 d1.days == d2.days &&
404                 d1.hours == d2.hours &&
405                 d1.minutes == d2.minutes &&
406                 d1.seconds == d2.seconds &&
407                 d1.microseconds == d2.microseconds;
408     }
409
410     public int hashCode() {
411         return new Double JavaDoc(getLengthInSeconds()).hashCode();
412     }
413
414     /**
415      * Compare two values for equality. This supports the matching rules in XML Schema, which say that two
416      * durations are equal only if all six components are equal (thus 12 months does not equal one year).
417      */

418
419     public boolean schemaEquals(Value obj) {
420         if (obj instanceof AtomicValue) {
421             obj = ((AtomicValue)obj).getPrimitiveValue();
422         }
423         if (obj instanceof DurationValue) {
424             DurationValue d = (DurationValue)obj;
425             return years == d.years &&
426                     months == d.months &&
427                     days == d.days &&
428                     hours == d.hours &&
429                     minutes == d.minutes &&
430                     seconds == d.seconds &&
431                     microseconds == d.microseconds;
432         } else {
433             return false;
434         }
435     }
436
437     /**
438     * Add two durations
439     */

440
441     public DurationValue add(DurationValue other, XPathContext context) throws XPathException {
442         throw new DynamicError("Only subtypes of xs:duration can be added");
443     }
444
445     /**
446     * Subtract two durations
447     */

448
449     public DurationValue subtract(DurationValue other, XPathContext context) throws XPathException{
450         throw new DynamicError("Only subtypes of xs:duration can be subtracted");
451     }
452
453     /**
454     * Multiply a duration by a number
455     */

456
457     public DurationValue multiply(double factor, XPathContext context) throws XPathException {
458         throw new DynamicError("Only subtypes of xs:duration can be multiplied by a number");
459     }
460
461     /**
462     * Divide a duration by a number
463     */

464
465     public DoubleValue divide(DurationValue other, XPathContext context) throws XPathException {
466         throw new DynamicError("Only subtypes of xs:duration can be divided by another duration");
467     }
468
469 }
470
471 //
472
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
473
// you may not use this file except in compliance with the License. You may obtain a copy of the
474
// License at http://www.mozilla.org/MPL/
475
//
476
// Software distributed under the License is distributed on an "AS IS" basis,
477
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
478
// See the License for the specific language governing rights and limitations under the License.
479
//
480
// The Original Code is: all this file.
481
//
482
// The Initial Developer of the Original Code is Michael H. Kay
483
//
484
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
485
//
486
// Contributor(s): none.
487
//
488

489
Popular Tags