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     */
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<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    }