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 */
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<ReadablePartial> {
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: Prior to v2.0, the {@code Comparable} interface was only implemented
301     * in this class and not in the {@code ReadablePartial} interface.
302     *
303     * @param other  an object to check against
304     * @return negative if this is less, zero if equal, positive if greater
305     * @throws ClassCastException if the partial is the wrong class
306     *  or if it has field types that don't match
307     * @throws NullPointerException if the partial is null
308     * @since 1.1
309     */
310    public int compareTo(ReadablePartial other) {
311        if (this == other) {
312            return 0;
313        }
314        if (size() != other.size()) {
315            throw new ClassCastException("ReadablePartial objects must have matching field types");
316        }
317        for (int i = 0, isize = size(); i < isize; i++) {
318            if (getFieldType(i) != other.getFieldType(i)) {
319                throw new ClassCastException("ReadablePartial objects must have matching field types");
320            }
321        }
322        // fields are ordered largest first
323        for (int i = 0, isize = size(); i < isize; i++) {
324            if (getValue(i) > other.getValue(i)) {
325                return 1;
326            }
327            if (getValue(i) < other.getValue(i)) {
328                return -1;
329            }
330        }
331        return 0;
332    }
333
334    /**
335     * Is this partial later than the specified partial.
336     * <p>
337     * The fields are compared in order, from largest to smallest.
338     * The first field that is non-equal is used to determine the result.
339     * <p>
340     * You may not pass null into this method. This is because you need
341     * a time zone to accurately determine the current date.
342     *
343     * @param partial  a partial to check against, must not be null
344     * @return true if this date is after the date passed in
345     * @throws IllegalArgumentException if the specified partial is null
346     * @throws ClassCastException if the partial has field types that don't match
347     * @since 1.1
348     */
349    public boolean isAfter(ReadablePartial partial) {
350        if (partial == null) {
351            throw new IllegalArgumentException("Partial cannot be null");
352        }
353        return compareTo(partial) > 0;
354    }
355
356    /**
357     * Is this partial earlier than the specified partial.
358     * <p>
359     * The fields are compared in order, from largest to smallest.
360     * The first field that is non-equal is used to determine the result.
361     * <p>
362     * You may not pass null into this method. This is because you need
363     * a time zone to accurately determine the current date.
364     *
365     * @param partial  a partial to check against, must not be null
366     * @return true if this date is before the date passed in
367     * @throws IllegalArgumentException if the specified partial is null
368     * @throws ClassCastException if the partial has field types that don't match
369     * @since 1.1
370     */
371    public boolean isBefore(ReadablePartial partial) {
372        if (partial == null) {
373            throw new IllegalArgumentException("Partial cannot be null");
374        }
375        return compareTo(partial) < 0;
376    }
377
378    /**
379     * Is this partial the same as the specified partial.
380     * <p>
381     * The fields are compared in order, from largest to smallest.
382     * If all fields are equal, the result is true.
383     * <p>
384     * You may not pass null into this method. This is because you need
385     * a time zone to accurately determine the current date.
386     *
387     * @param partial  a partial to check against, must not be null
388     * @return true if this date is the same as the date passed in
389     * @throws IllegalArgumentException if the specified partial is null
390     * @throws ClassCastException if the partial has field types that don't match
391     * @since 1.1
392     */
393    public boolean isEqual(ReadablePartial partial) {
394        if (partial == null) {
395            throw new IllegalArgumentException("Partial cannot be null");
396        }
397        return compareTo(partial) == 0;
398    }
399
400    //-----------------------------------------------------------------------
401    /**
402     * Uses the specified formatter to convert this partial to a String.
403     *
404     * @param formatter  the formatter to use, null means use <code>toString()</code>.
405     * @return the formatted string
406     * @since 1.1
407     */
408    public String toString(DateTimeFormatter formatter) {
409        if (formatter == null) {
410            return toString();
411        }
412        return formatter.print(this);
413    }
414
415}