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     */
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.chrono.ISOChronology;
032    import org.joda.time.convert.ConverterManager;
033    import org.joda.time.convert.PeriodConverter;
034    import 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     */
050    public 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    }