001: /*
002: * Copyright 2001-2007 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.base;
017:
018: import java.io.Serializable;
019:
020: import org.joda.time.Chronology;
021: import org.joda.time.DateTimeUtils;
022: import org.joda.time.Duration;
023: import org.joda.time.DurationFieldType;
024: import org.joda.time.MutablePeriod;
025: import org.joda.time.PeriodType;
026: import org.joda.time.ReadWritablePeriod;
027: import org.joda.time.ReadableDuration;
028: import org.joda.time.ReadableInstant;
029: import org.joda.time.ReadablePartial;
030: import org.joda.time.ReadablePeriod;
031: import org.joda.time.convert.ConverterManager;
032: import org.joda.time.convert.PeriodConverter;
033: import org.joda.time.field.FieldUtils;
034:
035: /**
036: * BasePeriod is an abstract implementation of ReadablePeriod that stores
037: * data in a <code>PeriodType</code> and an <code>int[]</code>.
038: * <p>
039: * This class should generally not be used directly by API users.
040: * The {@link ReadablePeriod} interface should be used when different
041: * kinds of period objects are to be referenced.
042: * <p>
043: * BasePeriod subclasses may be mutable and not thread-safe.
044: *
045: * @author Brian S O'Neill
046: * @author Stephen Colebourne
047: * @since 1.0
048: */
049: public abstract class BasePeriod extends AbstractPeriod implements
050: ReadablePeriod, Serializable {
051:
052: /** Serialization version */
053: private static final long serialVersionUID = -2110953284060001145L;
054:
055: /** The type of period */
056: private PeriodType iType;
057: /** The values */
058: private int[] iValues;
059:
060: //-----------------------------------------------------------------------
061: /**
062: * Creates a period from a set of field values.
063: *
064: * @param years amount of years in this period, which must be zero if unsupported
065: * @param months amount of months in this period, which must be zero if unsupported
066: * @param weeks amount of weeks in this period, which must be zero if unsupported
067: * @param days amount of days in this period, which must be zero if unsupported
068: * @param hours amount of hours in this period, which must be zero if unsupported
069: * @param minutes amount of minutes in this period, which must be zero if unsupported
070: * @param seconds amount of seconds in this period, which must be zero if unsupported
071: * @param millis amount of milliseconds in this period, which must be zero if unsupported
072: * @param type which set of fields this period supports
073: * @throws IllegalArgumentException if period type is invalid
074: * @throws IllegalArgumentException if an unsupported field's value is non-zero
075: */
076: protected BasePeriod(int years, int months, int weeks, int days,
077: int hours, int minutes, int seconds, int millis,
078: PeriodType type) {
079: super ();
080: type = checkPeriodType(type);
081: iType = type;
082: setPeriodInternal(years, months, weeks, days, hours, minutes,
083: seconds, millis); // internal method
084: }
085:
086: /**
087: * Creates a period from the given interval endpoints.
088: *
089: * @param startInstant interval start, in milliseconds
090: * @param endInstant interval end, in milliseconds
091: * @param type which set of fields this period supports, null means standard
092: * @param chrono the chronology to use, null means ISO default
093: * @throws IllegalArgumentException if period type is invalid
094: */
095: protected BasePeriod(long startInstant, long endInstant,
096: PeriodType type, Chronology chrono) {
097: super ();
098: type = checkPeriodType(type);
099: chrono = DateTimeUtils.getChronology(chrono);
100: iType = type;
101: iValues = chrono.get(this , startInstant, endInstant);
102: }
103:
104: /**
105: * Creates a period from the given interval endpoints.
106: *
107: * @param startInstant interval start, null means now
108: * @param endInstant interval end, null means now
109: * @param type which set of fields this period supports, null means standard
110: * @throws IllegalArgumentException if period type is invalid
111: */
112: protected BasePeriod(ReadableInstant startInstant,
113: ReadableInstant endInstant, PeriodType type) {
114: super ();
115: type = checkPeriodType(type);
116: if (startInstant == null && endInstant == null) {
117: iType = type;
118: iValues = new int[size()];
119: } else {
120: long startMillis = DateTimeUtils
121: .getInstantMillis(startInstant);
122: long endMillis = DateTimeUtils.getInstantMillis(endInstant);
123: Chronology chrono = DateTimeUtils.getIntervalChronology(
124: startInstant, endInstant);
125: iType = type;
126: iValues = chrono.get(this , startMillis, endMillis);
127: }
128: }
129:
130: /**
131: * Creates a period from the given duration and end point.
132: * <p>
133: * The two partials must contain the same fields, thus you can
134: * specify two <code>LocalDate</code> objects, or two <code>LocalTime</code>
135: * objects, but not one of each.
136: * As these are Partial objects, time zones have no effect on the result.
137: * <p>
138: * The two partials must also both be contiguous - see
139: * {@link DateTimeUtils#isContiguous(ReadablePartial)} for a
140: * definition. Both <code>LocalDate</code> and <code>LocalTime</code> are contiguous.
141: *
142: * @param start the start of the period, must not be null
143: * @param end the end of the period, must not be null
144: * @param type which set of fields this period supports, null means standard
145: * @throws IllegalArgumentException if the partials are null or invalid
146: * @since 1.1
147: */
148: protected BasePeriod(ReadablePartial start, ReadablePartial end,
149: PeriodType type) {
150: super ();
151: if (start == null || end == null) {
152: throw new IllegalArgumentException(
153: "ReadablePartial objects must not be null");
154: }
155: if (start instanceof BaseLocal && end instanceof BaseLocal
156: && start.getClass() == end.getClass()) {
157: // for performance
158: type = checkPeriodType(type);
159: long startMillis = ((BaseLocal) start).getLocalMillis();
160: long endMillis = ((BaseLocal) end).getLocalMillis();
161: Chronology chrono = start.getChronology();
162: chrono = DateTimeUtils.getChronology(chrono);
163: iType = type;
164: iValues = chrono.get(this , startMillis, endMillis);
165: } else {
166: if (start.size() != end.size()) {
167: throw new IllegalArgumentException(
168: "ReadablePartial objects must have the same set of fields");
169: }
170: for (int i = 0, isize = start.size(); i < isize; i++) {
171: if (start.getFieldType(i) != end.getFieldType(i)) {
172: throw new IllegalArgumentException(
173: "ReadablePartial objects must have the same set of fields");
174: }
175: }
176: if (DateTimeUtils.isContiguous(start) == false) {
177: throw new IllegalArgumentException(
178: "ReadablePartial objects must be contiguous");
179: }
180: iType = checkPeriodType(type);
181: Chronology chrono = DateTimeUtils.getChronology(
182: start.getChronology()).withUTC();
183: iValues = chrono.get(this , chrono.set(start, 0L), chrono
184: .set(end, 0L));
185: }
186: }
187:
188: /**
189: * Creates a period from the given start point and duration.
190: *
191: * @param startInstant the interval start, null means now
192: * @param duration the duration of the interval, null means zero-length
193: * @param type which set of fields this period supports, null means standard
194: */
195: protected BasePeriod(ReadableInstant startInstant,
196: ReadableDuration duration, PeriodType type) {
197: super ();
198: type = checkPeriodType(type);
199: long startMillis = DateTimeUtils.getInstantMillis(startInstant);
200: long durationMillis = DateTimeUtils.getDurationMillis(duration);
201: long endMillis = FieldUtils
202: .safeAdd(startMillis, durationMillis);
203: Chronology chrono = DateTimeUtils
204: .getInstantChronology(startInstant);
205: iType = type;
206: iValues = chrono.get(this , startMillis, endMillis);
207: }
208:
209: /**
210: * Creates a period from the given duration and end point.
211: *
212: * @param duration the duration of the interval, null means zero-length
213: * @param endInstant the interval end, null means now
214: * @param type which set of fields this period supports, null means standard
215: */
216: protected BasePeriod(ReadableDuration duration,
217: ReadableInstant endInstant, PeriodType type) {
218: super ();
219: type = checkPeriodType(type);
220: long durationMillis = DateTimeUtils.getDurationMillis(duration);
221: long endMillis = DateTimeUtils.getInstantMillis(endInstant);
222: long startMillis = FieldUtils.safeSubtract(endMillis,
223: durationMillis);
224: Chronology chrono = DateTimeUtils
225: .getInstantChronology(endInstant);
226: iType = type;
227: iValues = chrono.get(this , startMillis, endMillis);
228: }
229:
230: /**
231: * Creates a period from the given millisecond duration, which is only really
232: * suitable for durations less than one day.
233: * <p>
234: * Only fields that are precise will be used.
235: * Thus the largest precise field may have a large value.
236: *
237: * @param duration the duration, in milliseconds
238: * @param type which set of fields this period supports, null means standard
239: * @param chrono the chronology to use, null means ISO default
240: * @throws IllegalArgumentException if period type is invalid
241: */
242: protected BasePeriod(long duration, PeriodType type,
243: Chronology chrono) {
244: super ();
245: type = checkPeriodType(type);
246: chrono = DateTimeUtils.getChronology(chrono);
247: iType = type;
248: iValues = chrono.get(this , duration);
249: }
250:
251: /**
252: * Creates a new period based on another using the {@link ConverterManager}.
253: *
254: * @param period the period to convert
255: * @param type which set of fields this period supports, null means use type from object
256: * @param chrono the chronology to use, null means ISO default
257: * @throws IllegalArgumentException if period is invalid
258: * @throws IllegalArgumentException if an unsupported field's value is non-zero
259: */
260: protected BasePeriod(Object period, PeriodType type,
261: Chronology chrono) {
262: super ();
263: PeriodConverter converter = ConverterManager.getInstance()
264: .getPeriodConverter(period);
265: type = (type == null ? converter.getPeriodType(period) : type);
266: type = checkPeriodType(type);
267: iType = type;
268: if (this instanceof ReadWritablePeriod) {
269: iValues = new int[size()];
270: chrono = DateTimeUtils.getChronology(chrono);
271: converter
272: .setInto((ReadWritablePeriod) this , period, chrono);
273: } else {
274: iValues = new MutablePeriod(period, type, chrono)
275: .getValues();
276: }
277: }
278:
279: /**
280: * Constructor used when we trust ourselves.
281: * Do not expose publically.
282: *
283: * @param values the values to use, not null, not cloned
284: * @param type which set of fields this period supports, not null
285: */
286: protected BasePeriod(int[] values, PeriodType type) {
287: super ();
288: iType = type;
289: iValues = values;
290: }
291:
292: //-----------------------------------------------------------------------
293: /**
294: * Validates a period type, converting nulls to a default value and
295: * checking the type is suitable for this instance.
296: *
297: * @param type the type to check, may be null
298: * @return the validated type to use, not null
299: * @throws IllegalArgumentException if the period type is invalid
300: */
301: protected PeriodType checkPeriodType(PeriodType type) {
302: return DateTimeUtils.getPeriodType(type);
303: }
304:
305: //-----------------------------------------------------------------------
306: /**
307: * Gets the period type.
308: *
309: * @return the period type
310: */
311: public PeriodType getPeriodType() {
312: return iType;
313: }
314:
315: //-----------------------------------------------------------------------
316: /**
317: * Gets the number of fields that this period supports.
318: *
319: * @return the number of fields supported
320: */
321: public int size() {
322: return iType.size();
323: }
324:
325: /**
326: * Gets the field type at the specified index.
327: *
328: * @param index the index to retrieve
329: * @return the field at the specified index
330: * @throws IndexOutOfBoundsException if the index is invalid
331: */
332: public DurationFieldType getFieldType(int index) {
333: return iType.getFieldType(index);
334: }
335:
336: /**
337: * Gets the value at the specified index.
338: *
339: * @param index the index to retrieve
340: * @return the value of the field at the specified index
341: * @throws IndexOutOfBoundsException if the index is invalid
342: */
343: public int getValue(int index) {
344: return iValues[index];
345: }
346:
347: //-----------------------------------------------------------------------
348: /**
349: * Gets the total millisecond duration of this period relative to a start instant.
350: * <p>
351: * This method adds the period to the specified instant in order to
352: * calculate the duration.
353: * <p>
354: * An instant must be supplied as the duration of a period varies.
355: * For example, a period of 1 month could vary between the equivalent of
356: * 28 and 31 days in milliseconds due to different length months.
357: * Similarly, a day can vary at Daylight Savings cutover, typically between
358: * 23 and 25 hours.
359: *
360: * @param startInstant the instant to add the period to, thus obtaining the duration
361: * @return the total length of the period as a duration relative to the start instant
362: * @throws ArithmeticException if the millis exceeds the capacity of the duration
363: */
364: public Duration toDurationFrom(ReadableInstant startInstant) {
365: long startMillis = DateTimeUtils.getInstantMillis(startInstant);
366: Chronology chrono = DateTimeUtils
367: .getInstantChronology(startInstant);
368: long endMillis = chrono.add(this , startMillis, 1);
369: return new Duration(startMillis, endMillis);
370: }
371:
372: /**
373: * Gets the total millisecond duration of this period relative to an
374: * end instant.
375: * <p>
376: * This method subtracts the period from the specified instant in order
377: * to calculate the duration.
378: * <p>
379: * An instant must be supplied as the duration of a period varies.
380: * For example, a period of 1 month could vary between the equivalent of
381: * 28 and 31 days in milliseconds due to different length months.
382: * Similarly, a day can vary at Daylight Savings cutover, typically between
383: * 23 and 25 hours.
384: *
385: * @param endInstant the instant to subtract the period from, thus obtaining the duration
386: * @return the total length of the period as a duration relative to the end instant
387: * @throws ArithmeticException if the millis exceeds the capacity of the duration
388: */
389: public Duration toDurationTo(ReadableInstant endInstant) {
390: long endMillis = DateTimeUtils.getInstantMillis(endInstant);
391: Chronology chrono = DateTimeUtils
392: .getInstantChronology(endInstant);
393: long startMillis = chrono.add(this , endMillis, -1);
394: return new Duration(startMillis, endMillis);
395: }
396:
397: //-----------------------------------------------------------------------
398: /**
399: * Checks whether a field type is supported, and if so adds the new value
400: * to the relevent index in the specified array.
401: *
402: * @param type the field type
403: * @param values the array to update
404: * @param newValue the new value to store if successful
405: */
406: private void checkAndUpdate(DurationFieldType type, int[] values,
407: int newValue) {
408: int index = indexOf(type);
409: if (index == -1) {
410: if (newValue != 0) {
411: throw new IllegalArgumentException(
412: "Period does not support field '"
413: + type.getName() + "'");
414: }
415: } else {
416: values[index] = newValue;
417: }
418: }
419:
420: //-----------------------------------------------------------------------
421: /**
422: * Sets all the fields of this period from another.
423: *
424: * @param period the period to copy from, not null
425: * @throws IllegalArgumentException if an unsupported field's value is non-zero
426: */
427: protected void setPeriod(ReadablePeriod period) {
428: if (period == null) {
429: setValues(new int[size()]);
430: } else {
431: setPeriodInternal(period);
432: }
433: }
434:
435: /**
436: * Private method called from constructor.
437: */
438: private void setPeriodInternal(ReadablePeriod period) {
439: int[] newValues = new int[size()];
440: for (int i = 0, isize = period.size(); i < isize; i++) {
441: DurationFieldType type = period.getFieldType(i);
442: int value = period.getValue(i);
443: checkAndUpdate(type, newValues, value);
444: }
445: iValues = newValues;
446: }
447:
448: /**
449: * Sets the eight standard the fields in one go.
450: *
451: * @param years amount of years in this period, which must be zero if unsupported
452: * @param months amount of months in this period, which must be zero if unsupported
453: * @param weeks amount of weeks in this period, which must be zero if unsupported
454: * @param days amount of days in this period, which must be zero if unsupported
455: * @param hours amount of hours in this period, which must be zero if unsupported
456: * @param minutes amount of minutes in this period, which must be zero if unsupported
457: * @param seconds amount of seconds in this period, which must be zero if unsupported
458: * @param millis amount of milliseconds in this period, which must be zero if unsupported
459: * @throws IllegalArgumentException if an unsupported field's value is non-zero
460: */
461: protected void setPeriod(int years, int months, int weeks,
462: int days, int hours, int minutes, int seconds, int millis) {
463: setPeriodInternal(years, months, weeks, days, hours, minutes,
464: seconds, millis);
465: }
466:
467: /**
468: * Private method called from constructor.
469: */
470: private void setPeriodInternal(int years, int months, int weeks,
471: int days, int hours, int minutes, int seconds, int millis) {
472: int[] newValues = new int[size()];
473: checkAndUpdate(DurationFieldType.years(), newValues, years);
474: checkAndUpdate(DurationFieldType.months(), newValues, months);
475: checkAndUpdate(DurationFieldType.weeks(), newValues, weeks);
476: checkAndUpdate(DurationFieldType.days(), newValues, days);
477: checkAndUpdate(DurationFieldType.hours(), newValues, hours);
478: checkAndUpdate(DurationFieldType.minutes(), newValues, minutes);
479: checkAndUpdate(DurationFieldType.seconds(), newValues, seconds);
480: checkAndUpdate(DurationFieldType.millis(), newValues, millis);
481: iValues = newValues;
482: }
483:
484: //-----------------------------------------------------------------------
485: /**
486: * Sets the value of a field in this period.
487: *
488: * @param field the field to set
489: * @param value the value to set
490: * @throws IllegalArgumentException if field is is null or not supported.
491: */
492: protected void setField(DurationFieldType field, int value) {
493: setFieldInto(iValues, field, value);
494: }
495:
496: /**
497: * Sets the value of a field in this period.
498: *
499: * @param values the array of values to update
500: * @param field the field to set
501: * @param value the value to set
502: * @throws IllegalArgumentException if field is null or not supported.
503: */
504: protected void setFieldInto(int[] values, DurationFieldType field,
505: int value) {
506: int index = indexOf(field);
507: if (index == -1) {
508: if (value != 0 || field == null) {
509: throw new IllegalArgumentException(
510: "Period does not support field '" + field + "'");
511: }
512: } else {
513: values[index] = value;
514: }
515: }
516:
517: /**
518: * Adds the value of a field in this period.
519: *
520: * @param field the field to set
521: * @param value the value to set
522: * @throws IllegalArgumentException if field is is null or not supported.
523: */
524: protected void addField(DurationFieldType field, int value) {
525: addFieldInto(iValues, field, value);
526: }
527:
528: /**
529: * Adds the value of a field in this period.
530: *
531: * @param values the array of values to update
532: * @param field the field to set
533: * @param value the value to set
534: * @throws IllegalArgumentException if field is is null or not supported.
535: */
536: protected void addFieldInto(int[] values, DurationFieldType field,
537: int value) {
538: int index = indexOf(field);
539: if (index == -1) {
540: if (value != 0 || field == null) {
541: throw new IllegalArgumentException(
542: "Period does not support field '" + field + "'");
543: }
544: } else {
545: values[index] = FieldUtils.safeAdd(values[index], value);
546: }
547: }
548:
549: /**
550: * Merges the fields from another period.
551: *
552: * @param period the period to add from, not null
553: * @throws IllegalArgumentException if an unsupported field's value is non-zero
554: */
555: protected void mergePeriod(ReadablePeriod period) {
556: if (period != null) {
557: iValues = mergePeriodInto(getValues(), period);
558: }
559: }
560:
561: /**
562: * Merges the fields from another period.
563: *
564: * @param values the array of values to update
565: * @param period the period to add from, not null
566: * @return the updated values
567: * @throws IllegalArgumentException if an unsupported field's value is non-zero
568: */
569: protected int[] mergePeriodInto(int[] values, ReadablePeriod period) {
570: for (int i = 0, isize = period.size(); i < isize; i++) {
571: DurationFieldType type = period.getFieldType(i);
572: int value = period.getValue(i);
573: checkAndUpdate(type, values, value);
574: }
575: return values;
576: }
577:
578: /**
579: * Adds the fields from another period.
580: *
581: * @param period the period to add from, not null
582: * @throws IllegalArgumentException if an unsupported field's value is non-zero
583: */
584: protected void addPeriod(ReadablePeriod period) {
585: if (period != null) {
586: iValues = addPeriodInto(getValues(), period);
587: }
588: }
589:
590: /**
591: * Adds the fields from another period.
592: *
593: * @param values the array of values to update
594: * @param period the period to add from, not null
595: * @return the updated values
596: * @throws IllegalArgumentException if an unsupported field's value is non-zero
597: */
598: protected int[] addPeriodInto(int[] values, ReadablePeriod period) {
599: for (int i = 0, isize = period.size(); i < isize; i++) {
600: DurationFieldType type = period.getFieldType(i);
601: int value = period.getValue(i);
602: if (value != 0) {
603: int index = indexOf(type);
604: if (index == -1) {
605: throw new IllegalArgumentException(
606: "Period does not support field '"
607: + type.getName() + "'");
608: } else {
609: values[index] = FieldUtils.safeAdd(getValue(index),
610: value);
611: }
612: }
613: }
614: return values;
615: }
616:
617: //-----------------------------------------------------------------------
618: /**
619: * Sets the value of the field at the specifed index.
620: *
621: * @param index the index
622: * @param value the value to set
623: * @throws IndexOutOfBoundsException if the index is invalid
624: */
625: protected void setValue(int index, int value) {
626: iValues[index] = value;
627: }
628:
629: /**
630: * Sets the values of all fields.
631: *
632: * @param values the array of values
633: */
634: protected void setValues(int[] values) {
635: iValues = values;
636: }
637:
638: }
|