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