View Javadoc

1   /*
2    *  Copyright 2001-2005 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time.base;
17  
18  import org.joda.time.Chronology;
19  import org.joda.time.DateTime;
20  import org.joda.time.DateTimeField;
21  import org.joda.time.DateTimeFieldType;
22  import org.joda.time.DateTimeUtils;
23  import org.joda.time.DurationFieldType;
24  import org.joda.time.ReadableInstant;
25  import org.joda.time.ReadablePartial;
26  import org.joda.time.field.FieldUtils;
27  import org.joda.time.format.DateTimeFormatter;
28  
29  /***
30   * AbstractPartial provides a standard base implementation of most methods
31   * in the ReadablePartial interface.
32   * <p>
33   * Calculations on are performed using a {@link Chronology}.
34   * This chronology is set to be in the UTC time zone for all calculations.
35   * <p>
36   * The methods on this class use {@link ReadablePartial#size()},
37   * {@link AbstractPartial#getField(int, Chronology)} and
38   * {@link ReadablePartial#getValue(int)} to calculate their results.
39   * Subclasses may have a better implementation.
40   * <p>
41   * AbstractPartial allows subclasses may be mutable and not thread-safe.
42   *
43   * @author Stephen Colebourne
44   * @since 1.0
45   */
46  public abstract class AbstractPartial
47          implements ReadablePartial, Comparable {
48  
49      //-----------------------------------------------------------------------
50      /***
51       * Constructor.
52       */
53      protected AbstractPartial() {
54          super();
55      }
56  
57      //-----------------------------------------------------------------------
58      /***
59       * Gets the field for a specific index in the chronology specified.
60       * <p>
61       * This method must not use any instance variables.
62       * 
63       * @param index  the index to retrieve
64       * @param chrono  the chronology to use
65       * @return the field
66       * @throws IndexOutOfBoundsException if the index is invalid
67       */
68      protected abstract DateTimeField getField(int index, Chronology chrono);
69  
70      //-----------------------------------------------------------------------
71      /***
72       * Gets the field type at the specifed index.
73       * 
74       * @param index  the index
75       * @return the field type
76       * @throws IndexOutOfBoundsException if the index is invalid
77       */
78      public DateTimeFieldType getFieldType(int index) {
79          return getField(index, getChronology()).getType();
80      }
81  
82      /***
83       * Gets an array of the field types that this partial supports.
84       * <p>
85       * The fields are returned largest to smallest, for example Hour, Minute, Second.
86       *
87       * @return the fields supported in an array that may be altered, largest to smallest
88       */
89      public DateTimeFieldType[] getFieldTypes() {
90          DateTimeFieldType[] result = new DateTimeFieldType[size()];
91          for (int i = 0; i < result.length; i++) {
92              result[i] = getFieldType(i);
93          }
94          return result;
95      }
96  
97      /***
98       * Gets the field at the specifed index.
99       * 
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 }