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
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    }