001/*
002 *  Copyright 2001-2010 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 org.joda.convert.ToString;
019import org.joda.time.DurationFieldType;
020import org.joda.time.MutablePeriod;
021import org.joda.time.Period;
022import org.joda.time.ReadablePeriod;
023import org.joda.time.format.ISOPeriodFormat;
024import org.joda.time.format.PeriodFormatter;
025
026/**
027 * AbstractPeriod provides the common behaviour for period classes.
028 * <p>
029 * This class should generally not be used directly by API users. The 
030 * {@link ReadablePeriod} interface should be used when different 
031 * kinds of periods are to be referenced.
032 * <p>
033 * AbstractPeriod subclasses may be mutable and not thread-safe.
034 *
035 * @author Brian S O'Neill
036 * @author Stephen Colebourne
037 * @since 1.0
038 */
039public abstract class AbstractPeriod implements ReadablePeriod {
040
041    /**
042     * Constructor.
043     */
044    protected AbstractPeriod() {
045        super();
046    }
047
048    //-----------------------------------------------------------------------
049    /**
050     * Gets the number of fields that this period supports.
051     *
052     * @return the number of fields supported
053     * @since 2.0 (previously on BasePeriod)
054     */
055    public int size() {
056        return getPeriodType().size();
057    }
058
059    /**
060     * Gets the field type at the specified index.
061     *
062     * @param index  the index to retrieve
063     * @return the field at the specified index
064     * @throws IndexOutOfBoundsException if the index is invalid
065     * @since 2.0 (previously on BasePeriod)
066     */
067    public DurationFieldType getFieldType(int index) {
068        return getPeriodType().getFieldType(index);
069    }
070
071    /**
072     * Gets an array of the field types that this period supports.
073     * <p>
074     * The fields are returned largest to smallest, for example Hours, Minutes, Seconds.
075     *
076     * @return the fields supported in an array that may be altered, largest to smallest
077     */
078    public DurationFieldType[] getFieldTypes() {
079        DurationFieldType[] result = new DurationFieldType[size()];
080        for (int i = 0; i < result.length; i++) {
081            result[i] = getFieldType(i);
082        }
083        return result;
084    }
085
086    /**
087     * Gets an array of the value of each of the fields that this period supports.
088     * <p>
089     * The fields are returned largest to smallest, for example Hours, Minutes, Seconds.
090     * Each value corresponds to the same array index as <code>getFields()</code>
091     *
092     * @return the current values of each field in an array that may be altered, largest to smallest
093     */
094    public int[] getValues() {
095        int[] result = new int[size()];
096        for (int i = 0; i < result.length; i++) {
097            result[i] = getValue(i);
098        }
099        return result;
100    }
101
102    //-----------------------------------------------------------------------
103    /**
104     * Gets the value of one of the fields.
105     * <p>
106     * If the field type specified is not supported by the period then zero
107     * is returned.
108     *
109     * @param type  the field type to query, null returns zero
110     * @return the value of that field, zero if field not supported
111     */
112    public int get(DurationFieldType type) {
113        int index = indexOf(type);
114        if (index == -1) {
115            return 0;
116        }
117        return getValue(index);
118    }
119
120    /**
121     * Checks whether the field specified is supported by this period.
122     *
123     * @param type  the type to check, may be null which returns false
124     * @return true if the field is supported
125     */
126    public boolean isSupported(DurationFieldType type) {
127        return getPeriodType().isSupported(type);
128    }
129
130    /**
131     * Gets the index of the field in this period.
132     *
133     * @param type  the type to check, may be null which returns -1
134     * @return the index of -1 if not supported
135     */
136    public int indexOf(DurationFieldType type) {
137        return getPeriodType().indexOf(type);
138    }
139
140    //-----------------------------------------------------------------------
141    /**
142     * Get this period as an immutable <code>Period</code> object.
143     * 
144     * @return a Period using the same field set and values
145     */
146    public Period toPeriod() {
147        return new Period(this);
148    }
149
150    /**
151     * Get this object as a <code>MutablePeriod</code>.
152     * <p>
153     * This will always return a new <code>MutablePeriod</code> with the same fields.
154     * 
155     * @return a MutablePeriod using the same field set and values
156     */
157    public MutablePeriod toMutablePeriod() {
158        return new MutablePeriod(this);
159    }
160
161    //-----------------------------------------------------------------------
162    /**
163     * Compares this object with the specified object for equality based
164     * on the value of each field. All ReadablePeriod instances are accepted.
165     * <p>
166     * Note that a period of 1 day is not equal to a period of 24 hours,
167     * nor is 1 hour equal to 60 minutes. Only periods with the same amount
168     * in each field are equal.
169     * <p>
170     * This is because periods represent an abstracted definition of a time
171     * period (eg. a day may not actually be 24 hours, it might be 23 or 25
172     * at daylight savings boundary).
173     * <p>
174     * To compare the actual duration of two periods, convert both to
175     * {@link org.joda.time.Duration Duration}s, an operation that emphasises
176     * that the result may differ according to the date you choose.
177     *
178     * @param period  a readable period to check against
179     * @return true if all the field values are equal, false if
180     *  not or the period is null or of an incorrect type
181     */
182    public boolean equals(Object period) {
183        if (this == period) {
184            return true;
185        }
186        if (period instanceof ReadablePeriod == false) {
187            return false;
188        }
189        ReadablePeriod other = (ReadablePeriod) period;
190        if (size() != other.size()) {
191            return false;
192        }
193        for (int i = 0, isize = size(); i < isize; i++) {
194            if (getValue(i) != other.getValue(i) || getFieldType(i) != other.getFieldType(i)) {
195                return false;
196            }
197        }
198        return true;
199    }
200
201    /**
202     * Gets a hash code for the period as defined by ReadablePeriod.
203     *
204     * @return a hash code
205     */
206    public int hashCode() {
207        int total = 17;
208        for (int i = 0, isize = size(); i < isize; i++) {
209            total = 27 * total + getValue(i);
210            total = 27 * total + getFieldType(i).hashCode();
211        }
212        return total;
213    }
214
215    //-----------------------------------------------------------------------
216    /**
217     * Gets the value as a String in the ISO8601 duration format.
218     * <p>
219     * For example, "P6H3M7S" represents 6 hours, 3 minutes, 7 seconds.
220     * <p>
221     * For more control over the output, see
222     * {@link org.joda.time.format.PeriodFormatterBuilder PeriodFormatterBuilder}.
223     *
224     * @return the value as an ISO8601 string
225     */
226    @ToString
227    public String toString() {
228        return ISOPeriodFormat.standard().print(this);
229    }
230
231    //-----------------------------------------------------------------------
232    /**
233     * Uses the specified formatter to convert this period to a String.
234     *
235     * @param formatter  the formatter to use, null means use <code>toString()</code>.
236     * @return the formatted string
237     * @since 1.5
238     */
239    public String toString(PeriodFormatter formatter) {
240        if (formatter == null) {
241            return toString();
242        }
243        return formatter.print(this);
244    }
245
246}