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