001 /* 002 * Copyright 2001-2009 Stephen Colebourne 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.joda.time.chrono; 017 018 import java.util.HashMap; 019 import java.util.Locale; 020 021 import org.joda.time.Chronology; 022 import org.joda.time.DateTime; 023 import org.joda.time.DateTimeField; 024 import org.joda.time.DateTimeZone; 025 import org.joda.time.DurationField; 026 import org.joda.time.MutableDateTime; 027 import org.joda.time.ReadableDateTime; 028 import org.joda.time.field.DecoratedDateTimeField; 029 import org.joda.time.field.DecoratedDurationField; 030 import org.joda.time.field.FieldUtils; 031 import org.joda.time.format.DateTimeFormatter; 032 import org.joda.time.format.ISODateTimeFormat; 033 034 /** 035 * Wraps another Chronology to impose limits on the range of instants that 036 * the fields within a Chronology may support. The limits are applied to both 037 * DateTimeFields and DurationFields. 038 * <p> 039 * Methods in DateTimeField and DurationField throw an IllegalArgumentException 040 * whenever given an input instant that is outside the limits or when an 041 * attempt is made to move an instant outside the limits. 042 * <p> 043 * LimitChronology is thread-safe and immutable. 044 * 045 * @author Brian S O'Neill 046 * @author Stephen Colebourne 047 * @since 1.0 048 */ 049 public final class LimitChronology extends AssembledChronology { 050 051 /** Serialization lock */ 052 private static final long serialVersionUID = 7670866536893052522L; 053 054 /** 055 * Wraps another chronology, with datetime limits. When withUTC or 056 * withZone is called, the returned LimitChronology instance has 057 * the same limits, except they are time zone adjusted. 058 * 059 * @param base base chronology to wrap 060 * @param lowerLimit inclusive lower limit, or null if none 061 * @param upperLimit exclusive upper limit, or null if none 062 * @throws IllegalArgumentException if chronology is null or limits are invalid 063 */ 064 public static LimitChronology getInstance(Chronology base, 065 ReadableDateTime lowerLimit, 066 ReadableDateTime upperLimit) { 067 if (base == null) { 068 throw new IllegalArgumentException("Must supply a chronology"); 069 } 070 071 lowerLimit = lowerLimit == null ? null : lowerLimit.toDateTime(); 072 upperLimit = upperLimit == null ? null : upperLimit.toDateTime(); 073 074 if (lowerLimit != null && upperLimit != null) { 075 if (!lowerLimit.isBefore(upperLimit)) { 076 throw new IllegalArgumentException 077 ("The lower limit must be come before than the upper limit"); 078 } 079 } 080 081 return new LimitChronology(base, (DateTime)lowerLimit, (DateTime)upperLimit); 082 } 083 084 final DateTime iLowerLimit; 085 final DateTime iUpperLimit; 086 087 private transient LimitChronology iWithUTC; 088 089 /** 090 * Wraps another chronology, with datetime limits. When withUTC or 091 * withZone is called, the returned LimitChronology instance has 092 * the same limits, except they are time zone adjusted. 093 * 094 * @param lowerLimit inclusive lower limit, or null if none 095 * @param upperLimit exclusive upper limit, or null if none 096 */ 097 private LimitChronology(Chronology base, 098 DateTime lowerLimit, DateTime upperLimit) { 099 super(base, null); 100 // These can be set after assembly. 101 iLowerLimit = lowerLimit; 102 iUpperLimit = upperLimit; 103 } 104 105 /** 106 * Returns the inclusive lower limit instant. 107 * 108 * @return lower limit 109 */ 110 public DateTime getLowerLimit() { 111 return iLowerLimit; 112 } 113 114 /** 115 * Returns the inclusive upper limit instant. 116 * 117 * @return upper limit 118 */ 119 public DateTime getUpperLimit() { 120 return iUpperLimit; 121 } 122 123 /** 124 * If this LimitChronology is already UTC, then this is 125 * returned. Otherwise, a new instance is returned, with the limits 126 * adjusted to the new time zone. 127 */ 128 public Chronology withUTC() { 129 return withZone(DateTimeZone.UTC); 130 } 131 132 /** 133 * If this LimitChronology has the same time zone as the one given, then 134 * this is returned. Otherwise, a new instance is returned, with the limits 135 * adjusted to the new time zone. 136 */ 137 public Chronology withZone(DateTimeZone zone) { 138 if (zone == null) { 139 zone = DateTimeZone.getDefault(); 140 } 141 if (zone == getZone()) { 142 return this; 143 } 144 145 if (zone == DateTimeZone.UTC && iWithUTC != null) { 146 return iWithUTC; 147 } 148 149 DateTime lowerLimit = iLowerLimit; 150 if (lowerLimit != null) { 151 MutableDateTime mdt = lowerLimit.toMutableDateTime(); 152 mdt.setZoneRetainFields(zone); 153 lowerLimit = mdt.toDateTime(); 154 } 155 156 DateTime upperLimit = iUpperLimit; 157 if (upperLimit != null) { 158 MutableDateTime mdt = upperLimit.toMutableDateTime(); 159 mdt.setZoneRetainFields(zone); 160 upperLimit = mdt.toDateTime(); 161 } 162 163 LimitChronology chrono = getInstance 164 (getBase().withZone(zone), lowerLimit, upperLimit); 165 166 if (zone == DateTimeZone.UTC) { 167 iWithUTC = chrono; 168 } 169 170 return chrono; 171 } 172 173 public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth, 174 int millisOfDay) 175 throws IllegalArgumentException 176 { 177 long instant = getBase().getDateTimeMillis(year, monthOfYear, dayOfMonth, millisOfDay); 178 checkLimits(instant, "resulting"); 179 return instant; 180 } 181 182 public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth, 183 int hourOfDay, int minuteOfHour, 184 int secondOfMinute, int millisOfSecond) 185 throws IllegalArgumentException 186 { 187 long instant = getBase().getDateTimeMillis 188 (year, monthOfYear, dayOfMonth, 189 hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); 190 checkLimits(instant, "resulting"); 191 return instant; 192 } 193 194 public long getDateTimeMillis(long instant, 195 int hourOfDay, int minuteOfHour, 196 int secondOfMinute, int millisOfSecond) 197 throws IllegalArgumentException 198 { 199 checkLimits(instant, null); 200 instant = getBase().getDateTimeMillis 201 (instant, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); 202 checkLimits(instant, "resulting"); 203 return instant; 204 } 205 206 protected void assemble(Fields fields) { 207 // Keep a local cache of converted fields so as not to create redundant 208 // objects. 209 HashMap<Object, Object> converted = new HashMap<Object, Object>(); 210 211 // Convert duration fields... 212 213 fields.eras = convertField(fields.eras, converted); 214 fields.centuries = convertField(fields.centuries, converted); 215 fields.years = convertField(fields.years, converted); 216 fields.months = convertField(fields.months, converted); 217 fields.weekyears = convertField(fields.weekyears, converted); 218 fields.weeks = convertField(fields.weeks, converted); 219 fields.days = convertField(fields.days, converted); 220 221 fields.halfdays = convertField(fields.halfdays, converted); 222 fields.hours = convertField(fields.hours, converted); 223 fields.minutes = convertField(fields.minutes, converted); 224 fields.seconds = convertField(fields.seconds, converted); 225 fields.millis = convertField(fields.millis, converted); 226 227 // Convert datetime fields... 228 229 fields.year = convertField(fields.year, converted); 230 fields.yearOfEra = convertField(fields.yearOfEra, converted); 231 fields.yearOfCentury = convertField(fields.yearOfCentury, converted); 232 fields.centuryOfEra = convertField(fields.centuryOfEra, converted); 233 fields.era = convertField(fields.era, converted); 234 fields.dayOfWeek = convertField(fields.dayOfWeek, converted); 235 fields.dayOfMonth = convertField(fields.dayOfMonth, converted); 236 fields.dayOfYear = convertField(fields.dayOfYear, converted); 237 fields.monthOfYear = convertField(fields.monthOfYear, converted); 238 fields.weekOfWeekyear = convertField(fields.weekOfWeekyear, converted); 239 fields.weekyear = convertField(fields.weekyear, converted); 240 fields.weekyearOfCentury = convertField(fields.weekyearOfCentury, converted); 241 242 fields.millisOfSecond = convertField(fields.millisOfSecond, converted); 243 fields.millisOfDay = convertField(fields.millisOfDay, converted); 244 fields.secondOfMinute = convertField(fields.secondOfMinute, converted); 245 fields.secondOfDay = convertField(fields.secondOfDay, converted); 246 fields.minuteOfHour = convertField(fields.minuteOfHour, converted); 247 fields.minuteOfDay = convertField(fields.minuteOfDay, converted); 248 fields.hourOfDay = convertField(fields.hourOfDay, converted); 249 fields.hourOfHalfday = convertField(fields.hourOfHalfday, converted); 250 fields.clockhourOfDay = convertField(fields.clockhourOfDay, converted); 251 fields.clockhourOfHalfday = convertField(fields.clockhourOfHalfday, converted); 252 fields.halfdayOfDay = convertField(fields.halfdayOfDay, converted); 253 } 254 255 private DurationField convertField(DurationField field, HashMap<Object, Object> converted) { 256 if (field == null || !field.isSupported()) { 257 return field; 258 } 259 if (converted.containsKey(field)) { 260 return (DurationField)converted.get(field); 261 } 262 LimitDurationField limitField = new LimitDurationField(field); 263 converted.put(field, limitField); 264 return limitField; 265 } 266 267 private DateTimeField convertField(DateTimeField field, HashMap<Object, Object> converted) { 268 if (field == null || !field.isSupported()) { 269 return field; 270 } 271 if (converted.containsKey(field)) { 272 return (DateTimeField)converted.get(field); 273 } 274 LimitDateTimeField limitField = 275 new LimitDateTimeField(field, 276 convertField(field.getDurationField(), converted), 277 convertField(field.getRangeDurationField(), converted), 278 convertField(field.getLeapDurationField(), converted)); 279 converted.put(field, limitField); 280 return limitField; 281 } 282 283 void checkLimits(long instant, String desc) { 284 DateTime limit; 285 if ((limit = iLowerLimit) != null && instant < limit.getMillis()) { 286 throw new LimitException(desc, true); 287 } 288 if ((limit = iUpperLimit) != null && instant >= limit.getMillis()) { 289 throw new LimitException(desc, false); 290 } 291 } 292 293 //----------------------------------------------------------------------- 294 /** 295 * A limit chronology is only equal to a limit chronology with the 296 * same base chronology and limits. 297 * 298 * @param obj the object to compare to 299 * @return true if equal 300 * @since 1.4 301 */ 302 public boolean equals(Object obj) { 303 if (this == obj) { 304 return true; 305 } 306 if (obj instanceof LimitChronology == false) { 307 return false; 308 } 309 LimitChronology chrono = (LimitChronology) obj; 310 return 311 getBase().equals(chrono.getBase()) && 312 FieldUtils.equals(getLowerLimit(), chrono.getLowerLimit()) && 313 FieldUtils.equals(getUpperLimit(), chrono.getUpperLimit()); 314 } 315 316 /** 317 * A suitable hashcode for the chronology. 318 * 319 * @return the hashcode 320 * @since 1.4 321 */ 322 public int hashCode() { 323 int hash = 317351877; 324 hash += (getLowerLimit() != null ? getLowerLimit().hashCode() : 0); 325 hash += (getUpperLimit() != null ? getUpperLimit().hashCode() : 0); 326 hash += getBase().hashCode() * 7; 327 return hash; 328 } 329 330 /** 331 * A debugging string for the chronology. 332 * 333 * @return the debugging string 334 */ 335 public String toString() { 336 return "LimitChronology[" + getBase().toString() + ", " + 337 (getLowerLimit() == null ? "NoLimit" : getLowerLimit().toString()) + ", " + 338 (getUpperLimit() == null ? "NoLimit" : getUpperLimit().toString()) + ']'; 339 } 340 341 //----------------------------------------------------------------------- 342 /** 343 * Extends IllegalArgumentException such that the exception message is not 344 * generated unless it is actually requested. 345 */ 346 private class LimitException extends IllegalArgumentException { 347 private static final long serialVersionUID = -5924689995607498581L; 348 349 private final boolean iIsLow; 350 351 LimitException(String desc, boolean isLow) { 352 super(desc); 353 iIsLow = isLow; 354 } 355 356 public String getMessage() { 357 StringBuffer buf = new StringBuffer(85); 358 buf.append("The"); 359 String desc = super.getMessage(); 360 if (desc != null) { 361 buf.append(' '); 362 buf.append(desc); 363 } 364 buf.append(" instant is "); 365 366 DateTimeFormatter p = ISODateTimeFormat.dateTime(); 367 p = p.withChronology(getBase()); 368 if (iIsLow) { 369 buf.append("below the supported minimum of "); 370 p.printTo(buf, getLowerLimit().getMillis()); 371 } else { 372 buf.append("above the supported maximum of "); 373 p.printTo(buf, getUpperLimit().getMillis()); 374 } 375 376 buf.append(" ("); 377 buf.append(getBase()); 378 buf.append(')'); 379 380 return buf.toString(); 381 } 382 383 public String toString() { 384 return "IllegalArgumentException: " + getMessage(); 385 } 386 } 387 388 private class LimitDurationField extends DecoratedDurationField { 389 private static final long serialVersionUID = 8049297699408782284L; 390 391 LimitDurationField(DurationField field) { 392 super(field, field.getType()); 393 } 394 395 public int getValue(long duration, long instant) { 396 checkLimits(instant, null); 397 return getWrappedField().getValue(duration, instant); 398 } 399 400 public long getValueAsLong(long duration, long instant) { 401 checkLimits(instant, null); 402 return getWrappedField().getValueAsLong(duration, instant); 403 } 404 405 public long getMillis(int value, long instant) { 406 checkLimits(instant, null); 407 return getWrappedField().getMillis(value, instant); 408 } 409 410 public long getMillis(long value, long instant) { 411 checkLimits(instant, null); 412 return getWrappedField().getMillis(value, instant); 413 } 414 415 public long add(long instant, int amount) { 416 checkLimits(instant, null); 417 long result = getWrappedField().add(instant, amount); 418 checkLimits(result, "resulting"); 419 return result; 420 } 421 422 public long add(long instant, long amount) { 423 checkLimits(instant, null); 424 long result = getWrappedField().add(instant, amount); 425 checkLimits(result, "resulting"); 426 return result; 427 } 428 429 public int getDifference(long minuendInstant, long subtrahendInstant) { 430 checkLimits(minuendInstant, "minuend"); 431 checkLimits(subtrahendInstant, "subtrahend"); 432 return getWrappedField().getDifference(minuendInstant, subtrahendInstant); 433 } 434 435 public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { 436 checkLimits(minuendInstant, "minuend"); 437 checkLimits(subtrahendInstant, "subtrahend"); 438 return getWrappedField().getDifferenceAsLong(minuendInstant, subtrahendInstant); 439 } 440 441 } 442 443 private class LimitDateTimeField extends DecoratedDateTimeField { 444 private static final long serialVersionUID = -2435306746995699312L; 445 446 private final DurationField iDurationField; 447 private final DurationField iRangeDurationField; 448 private final DurationField iLeapDurationField; 449 450 LimitDateTimeField(DateTimeField field, 451 DurationField durationField, 452 DurationField rangeDurationField, 453 DurationField leapDurationField) { 454 super(field, field.getType()); 455 iDurationField = durationField; 456 iRangeDurationField = rangeDurationField; 457 iLeapDurationField = leapDurationField; 458 } 459 460 public int get(long instant) { 461 checkLimits(instant, null); 462 return getWrappedField().get(instant); 463 } 464 465 public String getAsText(long instant, Locale locale) { 466 checkLimits(instant, null); 467 return getWrappedField().getAsText(instant, locale); 468 } 469 470 public String getAsShortText(long instant, Locale locale) { 471 checkLimits(instant, null); 472 return getWrappedField().getAsShortText(instant, locale); 473 } 474 475 public long add(long instant, int amount) { 476 checkLimits(instant, null); 477 long result = getWrappedField().add(instant, amount); 478 checkLimits(result, "resulting"); 479 return result; 480 } 481 482 public long add(long instant, long amount) { 483 checkLimits(instant, null); 484 long result = getWrappedField().add(instant, amount); 485 checkLimits(result, "resulting"); 486 return result; 487 } 488 489 public long addWrapField(long instant, int amount) { 490 checkLimits(instant, null); 491 long result = getWrappedField().addWrapField(instant, amount); 492 checkLimits(result, "resulting"); 493 return result; 494 } 495 496 public int getDifference(long minuendInstant, long subtrahendInstant) { 497 checkLimits(minuendInstant, "minuend"); 498 checkLimits(subtrahendInstant, "subtrahend"); 499 return getWrappedField().getDifference(minuendInstant, subtrahendInstant); 500 } 501 502 public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { 503 checkLimits(minuendInstant, "minuend"); 504 checkLimits(subtrahendInstant, "subtrahend"); 505 return getWrappedField().getDifferenceAsLong(minuendInstant, subtrahendInstant); 506 } 507 508 public long set(long instant, int value) { 509 checkLimits(instant, null); 510 long result = getWrappedField().set(instant, value); 511 checkLimits(result, "resulting"); 512 return result; 513 } 514 515 public long set(long instant, String text, Locale locale) { 516 checkLimits(instant, null); 517 long result = getWrappedField().set(instant, text, locale); 518 checkLimits(result, "resulting"); 519 return result; 520 } 521 522 public final DurationField getDurationField() { 523 return iDurationField; 524 } 525 526 public final DurationField getRangeDurationField() { 527 return iRangeDurationField; 528 } 529 530 public boolean isLeap(long instant) { 531 checkLimits(instant, null); 532 return getWrappedField().isLeap(instant); 533 } 534 535 public int getLeapAmount(long instant) { 536 checkLimits(instant, null); 537 return getWrappedField().getLeapAmount(instant); 538 } 539 540 public final DurationField getLeapDurationField() { 541 return iLeapDurationField; 542 } 543 544 public long roundFloor(long instant) { 545 checkLimits(instant, null); 546 long result = getWrappedField().roundFloor(instant); 547 checkLimits(result, "resulting"); 548 return result; 549 } 550 551 public long roundCeiling(long instant) { 552 checkLimits(instant, null); 553 long result = getWrappedField().roundCeiling(instant); 554 checkLimits(result, "resulting"); 555 return result; 556 } 557 558 public long roundHalfFloor(long instant) { 559 checkLimits(instant, null); 560 long result = getWrappedField().roundHalfFloor(instant); 561 checkLimits(result, "resulting"); 562 return result; 563 } 564 565 public long roundHalfCeiling(long instant) { 566 checkLimits(instant, null); 567 long result = getWrappedField().roundHalfCeiling(instant); 568 checkLimits(result, "resulting"); 569 return result; 570 } 571 572 public long roundHalfEven(long instant) { 573 checkLimits(instant, null); 574 long result = getWrappedField().roundHalfEven(instant); 575 checkLimits(result, "resulting"); 576 return result; 577 } 578 579 public long remainder(long instant) { 580 checkLimits(instant, null); 581 long result = getWrappedField().remainder(instant); 582 checkLimits(result, "resulting"); 583 return result; 584 } 585 586 public int getMinimumValue(long instant) { 587 checkLimits(instant, null); 588 return getWrappedField().getMinimumValue(instant); 589 } 590 591 public int getMaximumValue(long instant) { 592 checkLimits(instant, null); 593 return getWrappedField().getMaximumValue(instant); 594 } 595 596 public int getMaximumTextLength(Locale locale) { 597 return getWrappedField().getMaximumTextLength(locale); 598 } 599 600 public int getMaximumShortTextLength(Locale locale) { 601 return getWrappedField().getMaximumShortTextLength(locale); 602 } 603 604 } 605 606 }