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 ; 14 15 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 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 55 56 public DurationValue(CharSequence s) throws XPathException { 57 StringTokenizer tok = new StringTokenizer (trimWhitespace(s).toString(), "-+.PYMDTHS", true); 58 try { 59 if (!tok.hasMoreElements()) badDuration("empty string", s); 60 String part = (String )tok.nextElement(); 61 if ("+".equals(part)) { 62 part = (String )tok.nextElement(); 63 } else if ("-".equals(part)) { 64 negative = true; 65 part = (String )tok.nextElement(); 66 } 67 if (!"P".equals(part)) badDuration("missing 'P'", s); 68 int state = 0; 69 while (tok.hasMoreElements()) { 70 part = (String )tok.nextElement(); 71 if ("T".equals(part)) { 72 state = 4; 73 part = (String )tok.nextElement(); 74 } 75 int value = Integer.parseInt(part); 76 if (!tok.hasMoreElements()) badDuration("missing unit letter at end", s); 77 char delim = ((String )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 128 } catch (NumberFormatException err) { 129 badDuration("non-numeric component", s); 130 } 131 } 132 133 protected void badDuration(String msg, CharSequence s) throws XPathException { 134 DynamicError err = new DynamicError("Invalid duration value '" + s + "' (" + msg + ')'); 135 err.setErrorCode("FORG0001"); 136 throw err; 137 } 138 139 145 146 public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) { 147 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 return MonthDurationValue.fromMonths((years*12 + months) * (negative ? -1 : +1)); 166 case Type.DAY_TIME_DURATION: 168 return new SecondsDurationValue((negative?-1:+1), days, hours, minutes, seconds, microseconds); 176 default: 178 ValidationException err = new ValidationException("Cannot convert duration to " + 179 requiredType.getDisplayName()); 180 err.setErrorCode("FORG0001"); 182 return new ValidationErrorValue(err); 183 } 184 } 185 186 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 217 218 public String getStringValue() { 219 return getStringValueCS().toString(); 220 } 221 222 226 227 public CharSequence getStringValueCS() { 228 229 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 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 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 310 311 public ItemType getItemType() { 312 return Type.DURATION_TYPE; 313 } 314 315 318 319 public Object convertToJava(Class target, XPathContext context) throws XPathException { 320 if (target.isAssignableFrom(DurationValue.class)) { 321 return this; 322 } else if (target==String .class || target==CharSequence .class) { 323 return getStringValue(); 324 } else if (target==Object .class) { 325 return getStringValue(); 326 } else { 327 Object 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 341 342 public AtomicValue getComponent(int component) throws XPathException { 343 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 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 ("Unknown component for duration: " + component); 364 } 365 } 366 367 368 377 378 389 393 394 public boolean equals(Object other) { 395 if (!(other instanceof DurationValue)) { 396 throw new ClassCastException ("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 (getLengthInSeconds()).hashCode(); 412 } 413 414 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 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 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 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 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 489 | Popular Tags |