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 implements ReadablePartial,
047: 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,
069: Chronology chrono);
070:
071: //-----------------------------------------------------------------------
072: /**
073: * Gets the field type at the specifed index.
074: *
075: * @param index the index
076: * @return the field type
077: * @throws IndexOutOfBoundsException if the index is invalid
078: */
079: public DateTimeFieldType getFieldType(int index) {
080: return getField(index, getChronology()).getType();
081: }
082:
083: /**
084: * Gets an array of the field types that this partial supports.
085: * <p>
086: * The fields are returned largest to smallest, for example Hour, Minute, Second.
087: *
088: * @return the fields supported in an array that may be altered, largest to smallest
089: */
090: public DateTimeFieldType[] getFieldTypes() {
091: DateTimeFieldType[] result = new DateTimeFieldType[size()];
092: for (int i = 0; i < result.length; i++) {
093: result[i] = getFieldType(i);
094: }
095: return result;
096: }
097:
098: /**
099: * Gets the field at the specifed index.
100: *
101: * @param index the index
102: * @return the field
103: * @throws IndexOutOfBoundsException if the index is invalid
104: */
105: public DateTimeField getField(int index) {
106: return getField(index, getChronology());
107: }
108:
109: /**
110: * Gets an array of the fields that this partial supports.
111: * <p>
112: * The fields are returned largest to smallest, for example Hour, Minute, Second.
113: *
114: * @return the fields supported in an array that may be altered, largest to smallest
115: */
116: public DateTimeField[] getFields() {
117: DateTimeField[] result = new DateTimeField[size()];
118: for (int i = 0; i < result.length; i++) {
119: result[i] = getField(i);
120: }
121: return result;
122: }
123:
124: /**
125: * Gets an array of the value of each of the fields that this partial supports.
126: * <p>
127: * The fields are returned largest to smallest, for example Hour, Minute, Second.
128: * Each value corresponds to the same array index as <code>getFields()</code>
129: *
130: * @return the current values of each field in an array that may be altered, largest to smallest
131: */
132: public int[] getValues() {
133: int[] result = new int[size()];
134: for (int i = 0; i < result.length; i++) {
135: result[i] = getValue(i);
136: }
137: return result;
138: }
139:
140: //-----------------------------------------------------------------------
141: /**
142: * Get the value of one of the fields of a datetime.
143: * <p>
144: * The field specified must be one of those that is supported by the partial.
145: *
146: * @param type a DateTimeFieldType instance that is supported by this partial
147: * @return the value of that field
148: * @throws IllegalArgumentException if the field is null or not supported
149: */
150: public int get(DateTimeFieldType type) {
151: return getValue(indexOfSupported(type));
152: }
153:
154: /**
155: * Checks whether the field specified is supported by this partial.
156: *
157: * @param type the type to check, may be null which returns false
158: * @return true if the field is supported
159: */
160: public boolean isSupported(DateTimeFieldType type) {
161: return (indexOf(type) != -1);
162: }
163:
164: /**
165: * Gets the index of the specified field, or -1 if the field is unsupported.
166: *
167: * @param type the type to check, may be null which returns -1
168: * @return the index of the field, -1 if unsupported
169: */
170: public int indexOf(DateTimeFieldType type) {
171: for (int i = 0, isize = size(); i < isize; i++) {
172: if (getFieldType(i) == type) {
173: return i;
174: }
175: }
176: return -1;
177: }
178:
179: /**
180: * Gets the index of the specified field, throwing an exception if the
181: * field is unsupported.
182: *
183: * @param type the type to check, not null
184: * @return the index of the field
185: * @throws IllegalArgumentException if the field is null or not supported
186: */
187: protected int indexOfSupported(DateTimeFieldType type) {
188: int index = indexOf(type);
189: if (index == -1) {
190: throw new IllegalArgumentException("Field '" + type
191: + "' is not supported");
192: }
193: return index;
194: }
195:
196: /**
197: * Gets the index of the first fields to have the specified duration,
198: * or -1 if the field is unsupported.
199: *
200: * @param type the type to check, may be null which returns -1
201: * @return the index of the field, -1 if unsupported
202: */
203: protected int indexOf(DurationFieldType type) {
204: for (int i = 0, isize = size(); i < isize; i++) {
205: if (getFieldType(i).getDurationType() == type) {
206: return i;
207: }
208: }
209: return -1;
210: }
211:
212: /**
213: * Gets the index of the first fields to have the specified duration,
214: * throwing an exception if the field is unsupported.
215: *
216: * @param type the type to check, not null
217: * @return the index of the field
218: * @throws IllegalArgumentException if the field is null or not supported
219: */
220: protected int indexOfSupported(DurationFieldType type) {
221: int index = indexOf(type);
222: if (index == -1) {
223: throw new IllegalArgumentException("Field '" + type
224: + "' is not supported");
225: }
226: return index;
227: }
228:
229: //-----------------------------------------------------------------------
230: /**
231: * Resolves this partial against another complete instant to create a new
232: * full instant. The combination is performed using the chronology of the
233: * specified instant.
234: * <p>
235: * For example, if this partial represents a time, then the result of this
236: * method will be the datetime from the specified base instant plus the
237: * time from this partial.
238: *
239: * @param baseInstant the instant that provides the missing fields, null means now
240: * @return the combined datetime
241: */
242: public DateTime toDateTime(ReadableInstant baseInstant) {
243: Chronology chrono = DateTimeUtils
244: .getInstantChronology(baseInstant);
245: long instantMillis = DateTimeUtils
246: .getInstantMillis(baseInstant);
247: long resolved = chrono.set(this , instantMillis);
248: return new DateTime(resolved, chrono);
249: }
250:
251: //-----------------------------------------------------------------------
252: /**
253: * Compares this ReadablePartial with another returning true if the chronology,
254: * field types and values are equal.
255: *
256: * @param partial an object to check against
257: * @return true if fields and values are equal
258: */
259: public boolean equals(Object partial) {
260: if (this == partial) {
261: return true;
262: }
263: if (partial instanceof ReadablePartial == false) {
264: return false;
265: }
266: ReadablePartial other = (ReadablePartial) partial;
267: if (size() != other.size()) {
268: return false;
269: }
270: for (int i = 0, isize = size(); i < isize; i++) {
271: if (getValue(i) != other.getValue(i)
272: || getFieldType(i) != other.getFieldType(i)) {
273: return false;
274: }
275: }
276: return FieldUtils
277: .equals(getChronology(), other.getChronology());
278: }
279:
280: /**
281: * Gets a hash code for the ReadablePartial that is compatible with the
282: * equals method.
283: *
284: * @return a suitable hash code
285: */
286: public int hashCode() {
287: int total = 157;
288: for (int i = 0, isize = size(); i < isize; i++) {
289: total = 23 * total + getValue(i);
290: total = 23 * total + getFieldType(i).hashCode();
291: }
292: total += getChronology().hashCode();
293: return total;
294: }
295:
296: //-----------------------------------------------------------------------
297: /**
298: * Compares this partial with another returning an integer
299: * indicating the order.
300: * <p>
301: * The fields are compared in order, from largest to smallest.
302: * The first field that is non-equal is used to determine the result.
303: * <p>
304: * The specified object must be a partial instance whose field types
305: * match those of this partial.
306: * <p>
307: * NOTE: This implementation violates the Comparable contract.
308: * This method will accept any instance of ReadablePartial as input.
309: * However, it is possible that some implementations of ReadablePartial
310: * exist that do not extend AbstractPartial, and thus will throw a
311: * ClassCastException if compared in the opposite direction.
312: * The cause of this problem is that ReadablePartial doesn't define
313: * the compareTo() method, however we can't change that until v2.0.
314: *
315: * @param partial an object to check against
316: * @return negative if this is less, zero if equal, positive if greater
317: * @throws ClassCastException if the partial is the wrong class
318: * or if it has field types that don't match
319: * @throws NullPointerException if the partial is null
320: * @since 1.1
321: */
322: public int compareTo(Object partial) {
323: if (this == partial) {
324: return 0;
325: }
326: ReadablePartial other = (ReadablePartial) partial;
327: if (size() != other.size()) {
328: throw new ClassCastException(
329: "ReadablePartial objects must have matching field types");
330: }
331: for (int i = 0, isize = size(); i < isize; i++) {
332: if (getFieldType(i) != other.getFieldType(i)) {
333: throw new ClassCastException(
334: "ReadablePartial objects must have matching field types");
335: }
336: }
337: // fields are ordered largest first
338: for (int i = 0, isize = size(); i < isize; i++) {
339: if (getValue(i) > other.getValue(i)) {
340: return 1;
341: }
342: if (getValue(i) < other.getValue(i)) {
343: return -1;
344: }
345: }
346: return 0;
347: }
348:
349: /**
350: * Is this partial later than the specified partial.
351: * <p>
352: * The fields are compared in order, from largest to smallest.
353: * The first field that is non-equal is used to determine the result.
354: * <p>
355: * You may not pass null into this method. This is because you need
356: * a time zone to accurately determine the current date.
357: *
358: * @param partial a partial to check against, must not be null
359: * @return true if this date is after the date passed in
360: * @throws IllegalArgumentException if the specified partial is null
361: * @throws ClassCastException if the partial has field types that don't match
362: * @since 1.1
363: */
364: public boolean isAfter(ReadablePartial partial) {
365: if (partial == null) {
366: throw new IllegalArgumentException("Partial cannot be null");
367: }
368: return compareTo(partial) > 0;
369: }
370:
371: /**
372: * Is this partial earlier than the specified partial.
373: * <p>
374: * The fields are compared in order, from largest to smallest.
375: * The first field that is non-equal is used to determine the result.
376: * <p>
377: * You may not pass null into this method. This is because you need
378: * a time zone to accurately determine the current date.
379: *
380: * @param partial a partial to check against, must not be null
381: * @return true if this date is before the date passed in
382: * @throws IllegalArgumentException if the specified partial is null
383: * @throws ClassCastException if the partial has field types that don't match
384: * @since 1.1
385: */
386: public boolean isBefore(ReadablePartial partial) {
387: if (partial == null) {
388: throw new IllegalArgumentException("Partial cannot be null");
389: }
390: return compareTo(partial) < 0;
391: }
392:
393: /**
394: * Is this partial the same as the specified partial.
395: * <p>
396: * The fields are compared in order, from largest to smallest.
397: * If all fields are equal, the result is true.
398: * <p>
399: * You may not pass null into this method. This is because you need
400: * a time zone to accurately determine the current date.
401: *
402: * @param partial a partial to check against, must not be null
403: * @return true if this date is the same as the date passed in
404: * @throws IllegalArgumentException if the specified partial is null
405: * @throws ClassCastException if the partial has field types that don't match
406: * @since 1.1
407: */
408: public boolean isEqual(ReadablePartial partial) {
409: if (partial == null) {
410: throw new IllegalArgumentException("Partial cannot be null");
411: }
412: return compareTo(partial) == 0;
413: }
414:
415: //-----------------------------------------------------------------------
416: /**
417: * Uses the specified formatter to convert this partial to a String.
418: *
419: * @param formatter the formatter to use, null means use <code>toString()</code>.
420: * @return the formatted string
421: * @since 1.1
422: */
423: public String toString(DateTimeFormatter formatter) {
424: if (formatter == null) {
425: return toString();
426: }
427: return formatter.print(this);
428: }
429:
430: }
|