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