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     */
016    package org.joda.time.base;
017    
018    import org.joda.time.Chronology;
019    import org.joda.time.DateTime;
020    import org.joda.time.DateTimeField;
021    import org.joda.time.DateTimeFieldType;
022    import org.joda.time.DateTimeUtils;
023    import org.joda.time.DurationFieldType;
024    import org.joda.time.ReadableInstant;
025    import org.joda.time.ReadablePartial;
026    import org.joda.time.field.FieldUtils;
027    import 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     */
046    public 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    }