001: /*
002: * Copyright 2001-2005 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, ReadableDateTime upperLimit) {
066: if (base == null) {
067: throw new IllegalArgumentException(
068: "Must supply a chronology");
069: }
070:
071: lowerLimit = lowerLimit == null ? null : lowerLimit
072: .toDateTime();
073: upperLimit = upperLimit == null ? null : upperLimit
074: .toDateTime();
075:
076: if (lowerLimit != null && upperLimit != null) {
077: if (!lowerLimit.isBefore(upperLimit)) {
078: throw new IllegalArgumentException(
079: "The lower limit must be come before than the upper limit");
080: }
081: }
082:
083: return new LimitChronology(base, (DateTime) lowerLimit,
084: (DateTime) upperLimit);
085: }
086:
087: final DateTime iLowerLimit;
088: final DateTime iUpperLimit;
089:
090: private transient LimitChronology iWithUTC;
091:
092: /**
093: * Wraps another chronology, with datetime limits. When withUTC or
094: * withZone is called, the returned LimitChronology instance has
095: * the same limits, except they are time zone adjusted.
096: *
097: * @param lowerLimit inclusive lower limit, or null if none
098: * @param upperLimit exclusive upper limit, or null if none
099: */
100: private LimitChronology(Chronology base, DateTime lowerLimit,
101: DateTime upperLimit) {
102: super (base, null);
103: // These can be set after assembly.
104: iLowerLimit = lowerLimit;
105: iUpperLimit = upperLimit;
106: }
107:
108: /**
109: * Returns the inclusive lower limit instant.
110: *
111: * @return lower limit
112: */
113: public DateTime getLowerLimit() {
114: return iLowerLimit;
115: }
116:
117: /**
118: * Returns the inclusive upper limit instant.
119: *
120: * @return upper limit
121: */
122: public DateTime getUpperLimit() {
123: return iUpperLimit;
124: }
125:
126: /**
127: * If this LimitChronology is already UTC, then this is
128: * returned. Otherwise, a new instance is returned, with the limits
129: * adjusted to the new time zone.
130: */
131: public Chronology withUTC() {
132: return withZone(DateTimeZone.UTC);
133: }
134:
135: /**
136: * If this LimitChronology has the same time zone as the one given, then
137: * this is returned. Otherwise, a new instance is returned, with the limits
138: * adjusted to the new time zone.
139: */
140: public Chronology withZone(DateTimeZone zone) {
141: if (zone == null) {
142: zone = DateTimeZone.getDefault();
143: }
144: if (zone == getZone()) {
145: return this ;
146: }
147:
148: if (zone == DateTimeZone.UTC && iWithUTC != null) {
149: return iWithUTC;
150: }
151:
152: DateTime lowerLimit = iLowerLimit;
153: if (lowerLimit != null) {
154: MutableDateTime mdt = lowerLimit.toMutableDateTime();
155: mdt.setZoneRetainFields(zone);
156: lowerLimit = mdt.toDateTime();
157: }
158:
159: DateTime upperLimit = iUpperLimit;
160: if (upperLimit != null) {
161: MutableDateTime mdt = upperLimit.toMutableDateTime();
162: mdt.setZoneRetainFields(zone);
163: upperLimit = mdt.toDateTime();
164: }
165:
166: LimitChronology chrono = getInstance(getBase().withZone(zone),
167: lowerLimit, upperLimit);
168:
169: if (zone == DateTimeZone.UTC) {
170: iWithUTC = chrono;
171: }
172:
173: return chrono;
174: }
175:
176: public long getDateTimeMillis(int year, int monthOfYear,
177: int dayOfMonth, int millisOfDay)
178: throws IllegalArgumentException {
179: long instant = getBase().getDateTimeMillis(year, monthOfYear,
180: dayOfMonth, millisOfDay);
181: checkLimits(instant, "resulting");
182: return instant;
183: }
184:
185: public long getDateTimeMillis(int year, int monthOfYear,
186: int dayOfMonth, int hourOfDay, int minuteOfHour,
187: int secondOfMinute, int millisOfSecond)
188: throws IllegalArgumentException {
189: long instant = getBase().getDateTimeMillis(year, monthOfYear,
190: dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute,
191: millisOfSecond);
192: checkLimits(instant, "resulting");
193: return instant;
194: }
195:
196: public long getDateTimeMillis(long instant, int hourOfDay,
197: int minuteOfHour, int secondOfMinute, int millisOfSecond)
198: throws IllegalArgumentException {
199: checkLimits(instant, null);
200: instant = getBase().getDateTimeMillis(instant, hourOfDay,
201: 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 converted = new HashMap();
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,
232: converted);
233: fields.centuryOfEra = convertField(fields.centuryOfEra,
234: converted);
235: fields.era = convertField(fields.era, converted);
236: fields.dayOfWeek = convertField(fields.dayOfWeek, converted);
237: fields.dayOfMonth = convertField(fields.dayOfMonth, converted);
238: fields.dayOfYear = convertField(fields.dayOfYear, converted);
239: fields.monthOfYear = convertField(fields.monthOfYear, converted);
240: fields.weekOfWeekyear = convertField(fields.weekOfWeekyear,
241: converted);
242: fields.weekyear = convertField(fields.weekyear, converted);
243: fields.weekyearOfCentury = convertField(
244: fields.weekyearOfCentury, converted);
245:
246: fields.millisOfSecond = convertField(fields.millisOfSecond,
247: converted);
248: fields.millisOfDay = convertField(fields.millisOfDay, converted);
249: fields.secondOfMinute = convertField(fields.secondOfMinute,
250: converted);
251: fields.secondOfDay = convertField(fields.secondOfDay, converted);
252: fields.minuteOfHour = convertField(fields.minuteOfHour,
253: converted);
254: fields.minuteOfDay = convertField(fields.minuteOfDay, converted);
255: fields.hourOfDay = convertField(fields.hourOfDay, converted);
256: fields.hourOfHalfday = convertField(fields.hourOfHalfday,
257: converted);
258: fields.clockhourOfDay = convertField(fields.clockhourOfDay,
259: converted);
260: fields.clockhourOfHalfday = convertField(
261: fields.clockhourOfHalfday, converted);
262: fields.halfdayOfDay = convertField(fields.halfdayOfDay,
263: converted);
264: }
265:
266: private DurationField convertField(DurationField field,
267: HashMap converted) {
268: if (field == null || !field.isSupported()) {
269: return field;
270: }
271: if (converted.containsKey(field)) {
272: return (DurationField) converted.get(field);
273: }
274: LimitDurationField limitField = new LimitDurationField(field);
275: converted.put(field, limitField);
276: return limitField;
277: }
278:
279: private DateTimeField convertField(DateTimeField field,
280: HashMap converted) {
281: if (field == null || !field.isSupported()) {
282: return field;
283: }
284: if (converted.containsKey(field)) {
285: return (DateTimeField) converted.get(field);
286: }
287: LimitDateTimeField limitField = new LimitDateTimeField(field,
288: convertField(field.getDurationField(), converted),
289: convertField(field.getRangeDurationField(), converted),
290: convertField(field.getLeapDurationField(), converted));
291: converted.put(field, limitField);
292: return limitField;
293: }
294:
295: void checkLimits(long instant, String desc) {
296: DateTime limit;
297: if ((limit = iLowerLimit) != null
298: && instant < limit.getMillis()) {
299: throw new LimitException(desc, true);
300: }
301: if ((limit = iUpperLimit) != null
302: && instant >= limit.getMillis()) {
303: throw new LimitException(desc, false);
304: }
305: }
306:
307: //-----------------------------------------------------------------------
308: /**
309: * A limit chronology is only equal to a limit chronology with the
310: * same base chronology and limits.
311: *
312: * @param obj the object to compare to
313: * @return true if equal
314: * @since 1.4
315: */
316: public boolean equals(Object obj) {
317: if (this == obj) {
318: return true;
319: }
320: if (obj instanceof LimitChronology == false) {
321: return false;
322: }
323: LimitChronology chrono = (LimitChronology) obj;
324: return getBase().equals(chrono.getBase())
325: && FieldUtils.equals(getLowerLimit(), chrono
326: .getLowerLimit())
327: && FieldUtils.equals(getUpperLimit(), chrono
328: .getUpperLimit());
329: }
330:
331: /**
332: * A suitable hashcode for the chronology.
333: *
334: * @return the hashcode
335: * @since 1.4
336: */
337: public int hashCode() {
338: int hash = 317351877;
339: hash += (getLowerLimit() != null ? getLowerLimit().hashCode()
340: : 0);
341: hash += (getUpperLimit() != null ? getUpperLimit().hashCode()
342: : 0);
343: hash += getBase().hashCode() * 7;
344: return hash;
345: }
346:
347: /**
348: * A debugging string for the chronology.
349: *
350: * @return the debugging string
351: */
352: public String toString() {
353: return "LimitChronology["
354: + getBase().toString()
355: + ", "
356: + (getLowerLimit() == null ? "NoLimit"
357: : getLowerLimit().toString())
358: + ", "
359: + (getUpperLimit() == null ? "NoLimit"
360: : getUpperLimit().toString()) + ']';
361: }
362:
363: //-----------------------------------------------------------------------
364: /**
365: * Extends IllegalArgumentException such that the exception message is not
366: * generated unless it is actually requested.
367: */
368: private class LimitException extends IllegalArgumentException {
369: private static final long serialVersionUID = -5924689995607498581L;
370:
371: private final boolean iIsLow;
372:
373: LimitException(String desc, boolean isLow) {
374: super (desc);
375: iIsLow = isLow;
376: }
377:
378: public String getMessage() {
379: StringBuffer buf = new StringBuffer(85);
380: buf.append("The");
381: String desc = super .getMessage();
382: if (desc != null) {
383: buf.append(' ');
384: buf.append(desc);
385: }
386: buf.append(" instant is ");
387:
388: DateTimeFormatter p = ISODateTimeFormat.dateTime();
389: p = p.withChronology(getBase());
390: if (iIsLow) {
391: buf.append("below the supported minimum of ");
392: p.printTo(buf, getLowerLimit().getMillis());
393: } else {
394: buf.append("above the supported maximum of ");
395: p.printTo(buf, getUpperLimit().getMillis());
396: }
397:
398: buf.append(" (");
399: buf.append(getBase());
400: buf.append(')');
401:
402: return buf.toString();
403: }
404:
405: public String toString() {
406: return "IllegalArgumentException: " + getMessage();
407: }
408: }
409:
410: private class LimitDurationField extends DecoratedDurationField {
411: private static final long serialVersionUID = 8049297699408782284L;
412:
413: LimitDurationField(DurationField field) {
414: super (field, field.getType());
415: }
416:
417: public int getValue(long duration, long instant) {
418: checkLimits(instant, null);
419: return getWrappedField().getValue(duration, instant);
420: }
421:
422: public long getValueAsLong(long duration, long instant) {
423: checkLimits(instant, null);
424: return getWrappedField().getValueAsLong(duration, instant);
425: }
426:
427: public long getMillis(int value, long instant) {
428: checkLimits(instant, null);
429: return getWrappedField().getMillis(value, instant);
430: }
431:
432: public long getMillis(long value, long instant) {
433: checkLimits(instant, null);
434: return getWrappedField().getMillis(value, instant);
435: }
436:
437: public long add(long instant, int amount) {
438: checkLimits(instant, null);
439: long result = getWrappedField().add(instant, amount);
440: checkLimits(result, "resulting");
441: return result;
442: }
443:
444: public long add(long instant, long amount) {
445: checkLimits(instant, null);
446: long result = getWrappedField().add(instant, amount);
447: checkLimits(result, "resulting");
448: return result;
449: }
450:
451: public int getDifference(long minuendInstant,
452: long subtrahendInstant) {
453: checkLimits(minuendInstant, "minuend");
454: checkLimits(subtrahendInstant, "subtrahend");
455: return getWrappedField().getDifference(minuendInstant,
456: subtrahendInstant);
457: }
458:
459: public long getDifferenceAsLong(long minuendInstant,
460: long subtrahendInstant) {
461: checkLimits(minuendInstant, "minuend");
462: checkLimits(subtrahendInstant, "subtrahend");
463: return getWrappedField().getDifferenceAsLong(
464: minuendInstant, subtrahendInstant);
465: }
466:
467: }
468:
469: private class LimitDateTimeField extends DecoratedDateTimeField {
470: private static final long serialVersionUID = -2435306746995699312L;
471:
472: private final DurationField iDurationField;
473: private final DurationField iRangeDurationField;
474: private final DurationField iLeapDurationField;
475:
476: LimitDateTimeField(DateTimeField field,
477: DurationField durationField,
478: DurationField rangeDurationField,
479: DurationField leapDurationField) {
480: super (field, field.getType());
481: iDurationField = durationField;
482: iRangeDurationField = rangeDurationField;
483: iLeapDurationField = leapDurationField;
484: }
485:
486: public int get(long instant) {
487: checkLimits(instant, null);
488: return getWrappedField().get(instant);
489: }
490:
491: public String getAsText(long instant, Locale locale) {
492: checkLimits(instant, null);
493: return getWrappedField().getAsText(instant, locale);
494: }
495:
496: public String getAsShortText(long instant, Locale locale) {
497: checkLimits(instant, null);
498: return getWrappedField().getAsShortText(instant, locale);
499: }
500:
501: public long add(long instant, int amount) {
502: checkLimits(instant, null);
503: long result = getWrappedField().add(instant, amount);
504: checkLimits(result, "resulting");
505: return result;
506: }
507:
508: public long add(long instant, long amount) {
509: checkLimits(instant, null);
510: long result = getWrappedField().add(instant, amount);
511: checkLimits(result, "resulting");
512: return result;
513: }
514:
515: public long addWrapField(long instant, int amount) {
516: checkLimits(instant, null);
517: long result = getWrappedField().addWrapField(instant,
518: amount);
519: checkLimits(result, "resulting");
520: return result;
521: }
522:
523: public int getDifference(long minuendInstant,
524: long subtrahendInstant) {
525: checkLimits(minuendInstant, "minuend");
526: checkLimits(subtrahendInstant, "subtrahend");
527: return getWrappedField().getDifference(minuendInstant,
528: subtrahendInstant);
529: }
530:
531: public long getDifferenceAsLong(long minuendInstant,
532: long subtrahendInstant) {
533: checkLimits(minuendInstant, "minuend");
534: checkLimits(subtrahendInstant, "subtrahend");
535: return getWrappedField().getDifferenceAsLong(
536: minuendInstant, subtrahendInstant);
537: }
538:
539: public long set(long instant, int value) {
540: checkLimits(instant, null);
541: long result = getWrappedField().set(instant, value);
542: checkLimits(result, "resulting");
543: return result;
544: }
545:
546: public long set(long instant, String text, Locale locale) {
547: checkLimits(instant, null);
548: long result = getWrappedField().set(instant, text, locale);
549: checkLimits(result, "resulting");
550: return result;
551: }
552:
553: public final DurationField getDurationField() {
554: return iDurationField;
555: }
556:
557: public final DurationField getRangeDurationField() {
558: return iRangeDurationField;
559: }
560:
561: public boolean isLeap(long instant) {
562: checkLimits(instant, null);
563: return getWrappedField().isLeap(instant);
564: }
565:
566: public int getLeapAmount(long instant) {
567: checkLimits(instant, null);
568: return getWrappedField().getLeapAmount(instant);
569: }
570:
571: public final DurationField getLeapDurationField() {
572: return iLeapDurationField;
573: }
574:
575: public long roundFloor(long instant) {
576: checkLimits(instant, null);
577: long result = getWrappedField().roundFloor(instant);
578: checkLimits(result, "resulting");
579: return result;
580: }
581:
582: public long roundCeiling(long instant) {
583: checkLimits(instant, null);
584: long result = getWrappedField().roundCeiling(instant);
585: checkLimits(result, "resulting");
586: return result;
587: }
588:
589: public long roundHalfFloor(long instant) {
590: checkLimits(instant, null);
591: long result = getWrappedField().roundHalfFloor(instant);
592: checkLimits(result, "resulting");
593: return result;
594: }
595:
596: public long roundHalfCeiling(long instant) {
597: checkLimits(instant, null);
598: long result = getWrappedField().roundHalfCeiling(instant);
599: checkLimits(result, "resulting");
600: return result;
601: }
602:
603: public long roundHalfEven(long instant) {
604: checkLimits(instant, null);
605: long result = getWrappedField().roundHalfEven(instant);
606: checkLimits(result, "resulting");
607: return result;
608: }
609:
610: public long remainder(long instant) {
611: checkLimits(instant, null);
612: long result = getWrappedField().remainder(instant);
613: checkLimits(result, "resulting");
614: return result;
615: }
616:
617: public int getMinimumValue(long instant) {
618: checkLimits(instant, null);
619: return getWrappedField().getMinimumValue(instant);
620: }
621:
622: public int getMaximumValue(long instant) {
623: checkLimits(instant, null);
624: return getWrappedField().getMaximumValue(instant);
625: }
626:
627: public int getMaximumTextLength(Locale locale) {
628: return getWrappedField().getMaximumTextLength(locale);
629: }
630:
631: public int getMaximumShortTextLength(Locale locale) {
632: return getWrappedField().getMaximumShortTextLength(locale);
633: }
634:
635: }
636:
637: }
|