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