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 }