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.Chronology;
019import org.joda.time.DateTime;
020import org.joda.time.DateTimeField;
021import org.joda.time.DateTimeFieldType;
022import org.joda.time.DateTimeUtils;
023import org.joda.time.DurationFieldType;
024import org.joda.time.ReadableInstant;
025import org.joda.time.ReadablePartial;
026import org.joda.time.field.FieldUtils;
027import org.joda.time.format.DateTimeFormatter;
028
029/**
030 * AbstractPartial provides a standard base implementation of most methods
031 * in the ReadablePartial interface.
032 * <p>
033 * Calculations on are performed using a {@link Chronology}.
034 * This chronology is set to be in the UTC time zone for all calculations.
035 * <p>
036 * The methods on this class use {@link ReadablePartial#size()},
037 * {@link AbstractPartial#getField(int, Chronology)} and
038 * {@link ReadablePartial#getValue(int)} to calculate their results.
039 * Subclasses may have a better implementation.
040 * <p>
041 * AbstractPartial allows subclasses may be mutable and not thread-safe.
042 *
043 * @author Stephen Colebourne
044 * @since 1.0
045 */
046public abstract class AbstractPartial
047        implements ReadablePartial, Comparable {
048
049    //-----------------------------------------------------------------------
050    /**
051     * Constructor.
052     */
053    protected AbstractPartial() {
054        super();
055    }
056
057    //-----------------------------------------------------------------------
058    /**
059     * Gets the field for a specific index in the chronology specified.
060     * <p>
061     * This method must not use any instance variables.
062     * 
063     * @param index  the index to retrieve
064     * @param chrono  the chronology to use
065     * @return the field
066     * @throws IndexOutOfBoundsException if the index is invalid
067     */
068    protected abstract DateTimeField getField(int index, Chronology chrono);
069
070    //-----------------------------------------------------------------------
071    /**
072     * Gets the field type at the specifed index.
073     * 
074     * @param index  the index
075     * @return the field type
076     * @throws IndexOutOfBoundsException if the index is invalid
077     */
078    public DateTimeFieldType getFieldType(int index) {
079        return getField(index, getChronology()).getType();
080    }
081
082    /**
083     * Gets an array of the field types that this partial supports.
084     * <p>
085     * The fields are returned largest to smallest, for example Hour, Minute, Second.
086     *
087     * @return the fields supported in an array that may be altered, largest to smallest
088     */
089    public DateTimeFieldType[] getFieldTypes() {
090        DateTimeFieldType[] result = new DateTimeFieldType[size()];
091        for (int i = 0; i < result.length; i++) {
092            result[i] = getFieldType(i);
093        }
094        return result;
095    }
096
097    /**
098     * Gets the field at the specifed index.
099     * 
100     * @param index  the index
101     * @return the field
102     * @throws IndexOutOfBoundsException if the index is invalid
103     */
104    public DateTimeField getField(int index) {
105        return getField(index, getChronology());
106    }
107
108    /**
109     * Gets an array of the fields that this partial supports.
110     * <p>
111     * The fields are returned largest to smallest, for example Hour, Minute, Second.
112     *
113     * @return the fields supported in an array that may be altered, largest to smallest
114     */
115    public DateTimeField[] getFields() {
116        DateTimeField[] result = new DateTimeField[size()];
117        for (int i = 0; i < result.length; i++) {
118            result[i] = getField(i);
119        }
120        return result;
121    }
122
123    /**
124     * Gets an array of the value of each of the fields that this partial supports.
125     * <p>
126     * The fields are returned largest to smallest, for example Hour, Minute, Second.
127     * Each value corresponds to the same array index as <code>getFields()</code>
128     *
129     * @return the current values of each field in an array that may be altered, largest to smallest
130     */
131    public int[] getValues() {
132        int[] result = new int[size()];
133        for (int i = 0; i < result.length; i++) {
134            result[i] = getValue(i);
135        }
136        return result;
137    }
138
139    //-----------------------------------------------------------------------
140    /**
141     * Get the value of one of the fields of a datetime.
142     * <p>
143     * The field specified must be one of those that is supported by the partial.
144     *
145     * @param type  a DateTimeFieldType instance that is supported by this partial
146     * @return the value of that field
147     * @throws IllegalArgumentException if the field is null or not supported
148     */
149    public int get(DateTimeFieldType type) {
150        return getValue(indexOfSupported(type));
151    }
152
153    /**
154     * Checks whether the field specified is supported by this partial.
155     *
156     * @param type  the type to check, may be null which returns false
157     * @return true if the field is supported
158     */
159    public boolean isSupported(DateTimeFieldType type) {
160        return (indexOf(type) != -1);
161    }
162
163    /**
164     * Gets the index of the specified field, or -1 if the field is unsupported.
165     *
166     * @param type  the type to check, may be null which returns -1
167     * @return the index of the field, -1 if unsupported
168     */
169    public int indexOf(DateTimeFieldType type) {
170        for (int i = 0, isize = size(); i < isize; i++) {
171            if (getFieldType(i) == type) {
172                return i;
173            }
174        }
175        return -1;
176    }
177
178    /**
179     * Gets the index of the specified field, throwing an exception if the
180     * field is unsupported.
181     *
182     * @param type  the type to check, not null
183     * @return the index of the field
184     * @throws IllegalArgumentException if the field is null or not supported
185     */
186    protected int indexOfSupported(DateTimeFieldType type) {
187        int index = indexOf(type);
188        if (index == -1) {
189            throw new IllegalArgumentException("Field '" + type + "' is not supported");
190        }
191        return index;
192    }
193
194    /**
195     * Gets the index of the first fields to have the specified duration,
196     * or -1 if the field is unsupported.
197     *
198     * @param type  the type to check, may be null which returns -1
199     * @return the index of the field, -1 if unsupported
200     */
201    protected int indexOf(DurationFieldType type) {
202        for (int i = 0, isize = size(); i < isize; i++) {
203            if (getFieldType(i).getDurationType() == type) {
204                return i;
205            }
206        }
207        return -1;
208    }
209
210    /**
211     * Gets the index of the first fields to have the specified duration,
212     * throwing an exception if the field is unsupported.
213     *
214     * @param type  the type to check, not null
215     * @return the index of the field
216     * @throws IllegalArgumentException if the field is null or not supported
217     */
218    protected int indexOfSupported(DurationFieldType type) {
219        int index = indexOf(type);
220        if (index == -1) {
221            throw new IllegalArgumentException("Field '" + type + "' is not supported");
222        }
223        return index;
224    }
225
226    //-----------------------------------------------------------------------
227    /**
228     * Resolves this partial against another complete instant to create a new
229     * full instant. The combination is performed using the chronology of the
230     * specified instant.
231     * <p>
232     * For example, if this partial represents a time, then the result of this
233     * method will be the datetime from the specified base instant plus the
234     * time from this partial.
235     *
236     * @param baseInstant  the instant that provides the missing fields, null means now
237     * @return the combined datetime
238     */
239    public DateTime toDateTime(ReadableInstant baseInstant) {
240        Chronology chrono = DateTimeUtils.getInstantChronology(baseInstant);
241        long instantMillis = DateTimeUtils.getInstantMillis(baseInstant);
242        long resolved = chrono.set(this, instantMillis);
243        return new DateTime(resolved, chrono);
244    }
245
246    //-----------------------------------------------------------------------
247    /**
248     * Compares this ReadablePartial with another returning true if the chronology,
249     * field types and values are equal.
250     *
251     * @param partial  an object to check against
252     * @return true if fields and values are equal
253     */
254    public boolean equals(Object partial) {
255        if (this == partial) {
256            return true;
257        }
258        if (partial instanceof ReadablePartial == false) {
259            return false;
260        }
261        ReadablePartial other = (ReadablePartial) partial;
262        if (size() != other.size()) {
263            return false;
264        }
265        for (int i = 0, isize = size(); i < isize; i++) {
266            if (getValue(i) != other.getValue(i) || getFieldType(i) != other.getFieldType(i)) {
267                return false;
268            }
269        }
270        return FieldUtils.equals(getChronology(), other.getChronology());
271    }
272
273    /**
274     * Gets a hash code for the ReadablePartial that is compatible with the 
275     * equals method.
276     *
277     * @return a suitable hash code
278     */
279    public int hashCode() {
280        int total = 157;
281        for (int i = 0, isize = size(); i < isize; i++) {
282            total = 23 * total + getValue(i);
283            total = 23 * total + getFieldType(i).hashCode();
284        }
285        total += getChronology().hashCode();
286        return total;
287    }
288
289    //-----------------------------------------------------------------------
290    /**
291     * Compares this partial with another returning an integer
292     * indicating the order.
293     * <p>
294     * The fields are compared in order, from largest to smallest.
295     * The first field that is non-equal is used to determine the result.
296     * <p>
297     * The specified object must be a partial instance whose field types
298     * match those of this partial.
299     * <p>
300     * NOTE: This implementation violates the Comparable contract.
301     * This method will accept any instance of ReadablePartial as input.
302     * However, it is possible that some implementations of ReadablePartial
303     * exist that do not extend AbstractPartial, and thus will throw a
304     * ClassCastException if compared in the opposite direction.
305     * The cause of this problem is that ReadablePartial doesn't define
306     * the compareTo() method, however we can't change that until v2.0.
307     *
308     * @param partial  an object to check against
309     * @return negative if this is less, zero if equal, positive if greater
310     * @throws ClassCastException if the partial is the wrong class
311     *  or if it has field types that don't match
312     * @throws NullPointerException if the partial is null
313     * @since 1.1
314     */
315    public int compareTo(Object partial) {
316        if (this == partial) {
317            return 0;
318        }
319        ReadablePartial other = (ReadablePartial) partial;
320        if (size() != other.size()) {
321            throw new ClassCastException("ReadablePartial objects must have matching field types");
322        }
323        for (int i = 0, isize = size(); i < isize; i++) {
324            if (getFieldType(i) != other.getFieldType(i)) {
325                throw new ClassCastException("ReadablePartial objects must have matching field types");
326            }
327        }
328        // fields are ordered largest first
329        for (int i = 0, isize = size(); i < isize; i++) {
330            if (getValue(i) > other.getValue(i)) {
331                return 1;
332            }
333            if (getValue(i) < other.getValue(i)) {
334                return -1;
335            }
336        }
337        return 0;
338    }
339
340    /**
341     * Is this partial later than the specified partial.
342     * <p>
343     * The fields are compared in order, from largest to smallest.
344     * The first field that is non-equal is used to determine the result.
345     * <p>
346     * You may not pass null into this method. This is because you need
347     * a time zone to accurately determine the current date.
348     *
349     * @param partial  a partial to check against, must not be null
350     * @return true if this date is after the date passed in
351     * @throws IllegalArgumentException if the specified partial is null
352     * @throws ClassCastException if the partial has field types that don't match
353     * @since 1.1
354     */
355    public boolean isAfter(ReadablePartial partial) {
356        if (partial == null) {
357            throw new IllegalArgumentException("Partial cannot be null");
358        }
359        return compareTo(partial) > 0;
360    }
361
362    /**
363     * Is this partial earlier than the specified partial.
364     * <p>
365     * The fields are compared in order, from largest to smallest.
366     * The first field that is non-equal is used to determine the result.
367     * <p>
368     * You may not pass null into this method. This is because you need
369     * a time zone to accurately determine the current date.
370     *
371     * @param partial  a partial to check against, must not be null
372     * @return true if this date is before the date passed in
373     * @throws IllegalArgumentException if the specified partial is null
374     * @throws ClassCastException if the partial has field types that don't match
375     * @since 1.1
376     */
377    public boolean isBefore(ReadablePartial partial) {
378        if (partial == null) {
379            throw new IllegalArgumentException("Partial cannot be null");
380        }
381        return compareTo(partial) < 0;
382    }
383
384    /**
385     * Is this partial the same as the specified partial.
386     * <p>
387     * The fields are compared in order, from largest to smallest.
388     * If all fields are equal, the result is true.
389     * <p>
390     * You may not pass null into this method. This is because you need
391     * a time zone to accurately determine the current date.
392     *
393     * @param partial  a partial to check against, must not be null
394     * @return true if this date is the same as the date passed in
395     * @throws IllegalArgumentException if the specified partial is null
396     * @throws ClassCastException if the partial has field types that don't match
397     * @since 1.1
398     */
399    public boolean isEqual(ReadablePartial partial) {
400        if (partial == null) {
401            throw new IllegalArgumentException("Partial cannot be null");
402        }
403        return compareTo(partial) == 0;
404    }
405
406    //-----------------------------------------------------------------------
407    /**
408     * Uses the specified formatter to convert this partial to a String.
409     *
410     * @param formatter  the formatter to use, null means use <code>toString()</code>.
411     * @return the formatted string
412     * @since 1.1
413     */
414    public String toString(DateTimeFormatter formatter) {
415        if (formatter == null) {
416            return toString();
417        }
418        return formatter.print(this);
419    }
420
421}