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 */ 016package org.joda.time.base; 017 018import org.joda.time.Chronology; 019import org.joda.time.DateTime; 020import org.joda.time.DateTimeField; 021import org.joda.time.DateTimeFieldType; 022import org.joda.time.DateTimeUtils; 023import org.joda.time.DurationFieldType; 024import org.joda.time.ReadableInstant; 025import org.joda.time.ReadablePartial; 026import org.joda.time.field.FieldUtils; 027import 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 */ 046public 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}