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.DurationField;
023    import org.joda.time.DurationFieldType;
024    import org.joda.time.MutablePeriod;
025    import org.joda.time.Period;
026    import org.joda.time.PeriodType;
027    import org.joda.time.ReadableInstant;
028    import org.joda.time.ReadablePartial;
029    import org.joda.time.ReadablePeriod;
030    import org.joda.time.chrono.ISOChronology;
031    import org.joda.time.field.FieldUtils;
032    
033    /**
034     * BaseSingleFieldPeriod is an abstract implementation of ReadablePeriod that
035     * manages a single duration field, such as days or minutes.
036     * <p>
037     * This class should generally not be used directly by API users.
038     * The {@link ReadablePeriod} interface should be used when different 
039     * kinds of period objects are to be referenced.
040     * <p>
041     * BaseSingleFieldPeriod subclasses may be mutable and not thread-safe.
042     *
043     * @author Stephen Colebourne
044     * @since 1.4
045     */
046    public abstract class BaseSingleFieldPeriod
047            implements ReadablePeriod, Comparable<BaseSingleFieldPeriod>, Serializable {
048    
049        /** Serialization version. */
050        private static final long serialVersionUID = 9386874258972L;
051    
052        /** The period in the units of this period. */
053        private volatile int iPeriod;
054    
055        //-----------------------------------------------------------------------
056        /**
057         * Calculates the number of whole units between the two specified datetimes.
058         *
059         * @param start  the start instant, validated to not be null
060         * @param end  the end instant, validated to not be null
061         * @param field  the field type to use, must not be null
062         * @return the period
063         * @throws IllegalArgumentException if the instants are null or invalid
064         */
065        protected static int between(ReadableInstant start, ReadableInstant end, DurationFieldType field) {
066            if (start == null || end == null) {
067                throw new IllegalArgumentException("ReadableInstant objects must not be null");
068            }
069            Chronology chrono = DateTimeUtils.getInstantChronology(start);
070            int amount = field.getField(chrono).getDifference(end.getMillis(), start.getMillis());
071            return amount;
072        }
073    
074        //-----------------------------------------------------------------------
075        /**
076         * Calculates the number of whole units between the two specified partial datetimes.
077         * <p>
078         * The two partials must contain the same fields, for example you can specify
079         * two <code>LocalDate</code> objects.
080         *
081         * @param start  the start partial date, validated to not be null
082         * @param end  the end partial date, validated to not be null
083         * @param zeroInstance  the zero instance constant, must not be null
084         * @return the period
085         * @throws IllegalArgumentException if the partials are null or invalid
086         */
087        protected static int between(ReadablePartial start, ReadablePartial end, ReadablePeriod zeroInstance) {
088            if (start == null || end == null) {
089                throw new IllegalArgumentException("ReadablePartial objects must not be null");
090            }
091            if (start.size() != end.size()) {
092                throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields");
093            }
094            for (int i = 0, isize = start.size(); i < isize; i++) {
095                if (start.getFieldType(i) != end.getFieldType(i)) {
096                    throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields");
097                }
098            }
099            if (DateTimeUtils.isContiguous(start) == false) {
100                throw new IllegalArgumentException("ReadablePartial objects must be contiguous");
101            }
102            Chronology chrono = DateTimeUtils.getChronology(start.getChronology()).withUTC();
103            int[] values = chrono.get(zeroInstance, chrono.set(start, 0L), chrono.set(end, 0L));
104            return values[0];
105        }
106    
107        /**
108         * Creates a new instance representing the number of complete standard length units
109         * in the specified period.
110         * <p>
111         * This factory method converts all fields from the period to hours using standardised
112         * durations for each field. Only those fields which have a precise duration in
113         * the ISO UTC chronology can be converted.
114         * <ul>
115         * <li>One week consists of 7 days.
116         * <li>One day consists of 24 hours.
117         * <li>One hour consists of 60 minutes.
118         * <li>One minute consists of 60 seconds.
119         * <li>One second consists of 1000 milliseconds.
120         * </ul>
121         * Months and Years are imprecise and periods containing these values cannot be converted.
122         *
123         * @param period  the period to get the number of hours from, must not be null
124         * @param millisPerUnit  the number of milliseconds in one standard unit of this period
125         * @throws IllegalArgumentException if the period contains imprecise duration values
126         */
127        protected static int standardPeriodIn(ReadablePeriod period, long millisPerUnit) {
128            if (period == null) {
129                return 0;
130            }
131            Chronology iso = ISOChronology.getInstanceUTC();
132            long duration = 0L;
133            for (int i = 0; i < period.size(); i++) {
134                int value = period.getValue(i);
135                if (value != 0) {
136                    DurationField field = period.getFieldType(i).getField(iso);
137                    if (field.isPrecise() == false) {
138                        throw new IllegalArgumentException(
139                                "Cannot convert period to duration as " + field.getName() +
140                                " is not precise in the period " + period);
141                    }
142                    duration = FieldUtils.safeAdd(duration, FieldUtils.safeMultiply(field.getUnitMillis(), value));
143                }
144            }
145            return FieldUtils.safeToInt(duration / millisPerUnit);
146        }
147    
148        //-----------------------------------------------------------------------
149        /**
150         * Creates a new instance representing the specified period.
151         *
152         * @param period  the period to represent
153         */
154        protected BaseSingleFieldPeriod(int period) {
155            super();
156            iPeriod = period;
157        }
158    
159        //-----------------------------------------------------------------------
160        /**
161         * Gets the amount of this period.
162         *
163         * @return the period value
164         */
165        protected int getValue() {
166            return iPeriod;
167        }
168    
169        /**
170         * Sets the amount of this period.
171         * To make a subclass immutable you must declare it final, or block this method.
172         *
173         * @param value  the period value
174         */
175        protected void setValue(int value) {
176            iPeriod = value;
177        }
178    
179        //-----------------------------------------------------------------------
180        /**
181         * Gets the single duration field type.
182         *
183         * @return the duration field type, not null
184         */
185        public abstract DurationFieldType getFieldType();
186    
187        /**
188         * Gets the period type which matches the duration field type.
189         *
190         * @return the period type, not null
191         */
192        public abstract PeriodType getPeriodType();
193    
194        //-----------------------------------------------------------------------
195        /**
196         * Gets the number of fields that this period supports, which is one.
197         *
198         * @return the number of fields supported, which is one
199         */
200        public int size() {
201            return 1;
202        }
203    
204        /**
205         * Gets the field type at the specified index.
206         * <p>
207         * The only index supported by this period is zero which returns the
208         * field type of this class.
209         *
210         * @param index  the index to retrieve, which must be zero
211         * @return the field at the specified index
212         * @throws IndexOutOfBoundsException if the index is invalid
213         */
214        public DurationFieldType getFieldType(int index) {
215            if (index != 0) {
216                throw new IndexOutOfBoundsException(String.valueOf(index));
217            }
218            return getFieldType();
219        }
220    
221        /**
222         * Gets the value at the specified index.
223         * <p>
224         * The only index supported by this period is zero.
225         *
226         * @param index  the index to retrieve, which must be zero
227         * @return the value of the field at the specified index
228         * @throws IndexOutOfBoundsException if the index is invalid
229         */
230        public int getValue(int index) {
231            if (index != 0) {
232                throw new IndexOutOfBoundsException(String.valueOf(index));
233            }
234            return getValue();
235        }
236    
237        /**
238         * Gets the value of a duration field represented by this period.
239         * <p>
240         * If the field type specified does not match the type used by this class
241         * then zero is returned.
242         *
243         * @param type  the field type to query, null returns zero
244         * @return the value of that field, zero if field not supported
245         */
246        public int get(DurationFieldType type) {
247            if (type == getFieldType()) {
248                return getValue();
249            }
250            return 0;
251        }
252    
253        /**
254         * Checks whether the duration field specified is supported by this period.
255         *
256         * @param type  the type to check, may be null which returns false
257         * @return true if the field is supported
258         */
259        public boolean isSupported(DurationFieldType type) {
260            return (type == getFieldType());
261        }
262    
263        //-----------------------------------------------------------------------
264        /**
265         * Get this period as an immutable <code>Period</code> object.
266         * The period will use <code>PeriodType.standard()</code>.
267         *
268         * @return a <code>Period</code> representing the same number of days
269         */
270        public Period toPeriod() {
271            return Period.ZERO.withFields(this);
272        }
273    
274        /**
275         * Get this object as a <code>MutablePeriod</code>.
276         * <p>
277         * This will always return a new <code>MutablePeriod</code> with the same fields.
278         * The period will use <code>PeriodType.standard()</code>.
279         * 
280         * @return a MutablePeriod using the same field set and values
281         */
282        public MutablePeriod toMutablePeriod() {
283            MutablePeriod period = new MutablePeriod();
284            period.add(this);
285            return period;
286        }
287    
288        //-----------------------------------------------------------------------
289        /**
290         * Compares this object with the specified object for equality based on the
291         * value of each field. All ReadablePeriod instances are accepted, but only
292         * those with a matching <code>PeriodType</code> can return true.
293         *
294         * @param period  a readable period to check against
295         * @return true if all the field values are equal, false if
296         *  not or the period is null or of an incorrect type
297         */
298        public boolean equals(Object period) {
299            if (this == period) {
300                return true;
301            }
302            if (period instanceof ReadablePeriod == false) {
303                return false;
304            }
305            ReadablePeriod other = (ReadablePeriod) period;
306            return (other.getPeriodType() == getPeriodType() && other.getValue(0) == getValue());
307        }
308    
309        /**
310         * Gets a hash code for the period as defined by ReadablePeriod.
311         *
312         * @return a hash code
313         */
314        public int hashCode() {
315            int total = 17;
316            total = 27 * total + getValue();
317            total = 27 * total + getFieldType().hashCode();
318            return total;
319        }
320    
321        /**
322         * Compares this period to another object of the same class.
323         *
324         * @param other  the other period, must not be null
325         * @return zero if equal, positive if greater, negative if less
326         * @throws NullPointerException if the other period is null
327         * @throws ClassCastException if the other period is of a different type
328         */
329        public int compareTo(BaseSingleFieldPeriod other) {
330            if (other.getClass() != getClass()) {
331                throw new ClassCastException(getClass() + " cannot be compared to " + other.getClass());
332            }
333            int otherValue = other.getValue();
334            int thisValue = getValue();
335            if (thisValue > otherValue) {
336                return 1;
337            }
338            if (thisValue < otherValue) {
339                return -1;
340            }
341            return 0;
342        }
343    
344    }