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     */
016    package org.joda.time.base;
017    
018    import org.joda.convert.ToString;
019    import org.joda.time.DurationFieldType;
020    import org.joda.time.MutablePeriod;
021    import org.joda.time.Period;
022    import org.joda.time.ReadablePeriod;
023    import org.joda.time.format.ISOPeriodFormat;
024    import 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     */
039    public 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    }