44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package java.time.format; 63 64 import java.time.DateTimeException; 65 import java.time.ZoneId; 66 import java.time.chrono.Chronology; 67 import java.time.chrono.IsoChronology; 68 import java.time.temporal.ChronoField; 69 import java.time.temporal.Queries; 70 import java.time.temporal.TemporalAccessor; 71 import java.time.temporal.TemporalField; 72 import java.time.temporal.TemporalQuery; 73 import java.util.ArrayList; 74 import java.util.HashMap; 75 import java.util.Locale; 76 import java.util.Map; 77 import java.util.Objects; 78 79 /** 80 * Context object used during date and time parsing. 81 * <p> 82 * This class represents the current state of the parse. 83 * It has the ability to store and retrieve the parsed values and manage optional segments. 84 * It also provides key information to the parsing methods. 85 * <p> 86 * Once parsing is complete, the {@link #toBuilder()} is typically used 87 * to obtain a builder that can combine the separate parsed fields into meaningful values. 88 * 89 * <h3>Specification for implementors</h3> 90 * This class is a mutable context intended for use from a single thread. 91 * Usage of the class is thread-safe within standard parsing as a new instance of this class 92 * is automatically created for each parse and parsing is single-threaded 93 * 94 * @since 1.8 95 */ 96 final class DateTimeParseContext implements TemporalAccessor { 97 98 /** 99 * The formatter, not null. 100 */ 101 private DateTimeFormatter formatter; 102 /** 103 * Whether to parse using case sensitively. 104 */ 105 private boolean caseSensitive = true; 106 /** 107 * Whether to parse using strict rules. 108 */ 109 private boolean strict = true; 110 /** 111 * The list of parsed data. 112 */ 113 private final ArrayList<Parsed> parsed = new ArrayList<>(); 114 115 /** 116 * Creates a new instance of the context. 289 * @param successful whether the optional segment was successfully parsed 290 */ 291 void endOptional(boolean successful) { 292 if (successful) { 293 parsed.remove(parsed.size() - 2); 294 } else { 295 parsed.remove(parsed.size() - 1); 296 } 297 } 298 299 //----------------------------------------------------------------------- 300 /** 301 * Gets the currently active temporal objects. 302 * 303 * @return the current temporal objects, not null 304 */ 305 private Parsed currentParsed() { 306 return parsed.get(parsed.size() - 1); 307 } 308 309 //----------------------------------------------------------------------- 310 /** 311 * Gets the first value that was parsed for the specified field. 312 * <p> 313 * This searches the results of the parse, returning the first value found 314 * for the specified field. No attempt is made to derive a value. 315 * The field may have an out of range value. 316 * For example, the day-of-month might be set to 50, or the hour to 1000. 317 * 318 * @param field the field to query from the map, null returns null 319 * @return the value mapped to the specified field, null if field was not parsed 320 */ 321 Long getParsed(TemporalField field) { 322 return currentParsed().fieldValues.get(field); 323 } 324 325 /** 326 * Stores the parsed field. 327 * <p> 328 * This stores a field-value pair that has been parsed. 351 void setParsed(Chronology chrono) { 352 Objects.requireNonNull(chrono, "chrono"); 353 currentParsed().chrono = chrono; 354 } 355 356 /** 357 * Stores the parsed zone. 358 * <p> 359 * This stores the zone that has been parsed. 360 * No validation is performed other than ensuring it is not null. 361 * 362 * @param zone the parsed zone, not null 363 */ 364 void setParsed(ZoneId zone) { 365 Objects.requireNonNull(zone, "zone"); 366 currentParsed().zone = zone; 367 } 368 369 //----------------------------------------------------------------------- 370 /** 371 * Returns a {@code DateTimeBuilder} that can be used to interpret 372 * the results of the parse. 373 * <p> 374 * This method is typically used once parsing is complete to obtain the parsed data. 375 * Parsing will typically result in separate fields, such as year, month and day. 376 * The returned builder can be used to combine the parsed data into meaningful 377 * objects such as {@code LocalDate}, potentially applying complex processing 378 * to handle invalid parsed data. 379 * 380 * @return a new builder with the results of the parse, not null 381 */ 382 DateTimeBuilder toBuilder() { 383 Parsed parsed = currentParsed(); 384 DateTimeBuilder builder = new DateTimeBuilder(); 385 for (Map.Entry<TemporalField, Long> fv : parsed.fieldValues.entrySet()) { 386 builder.addFieldValue(fv.getKey(), fv.getValue()); 387 } 388 builder.addObject(getEffectiveChronology()); 389 if (parsed.zone != null) { 390 builder.addObject(parsed.zone); 391 } 392 return builder; 393 } 394 395 /** 396 * Resolves the fields in this context. 397 * 398 * @return this, for method chaining 399 * @throws DateTimeException if resolving one field results in a value for 400 * another field that is in conflict 401 */ 402 DateTimeParseContext resolveFields() { 403 Parsed data = currentParsed(); 404 outer: 405 while (true) { 406 for (Map.Entry<TemporalField, Long> entry : data.fieldValues.entrySet()) { 407 TemporalField targetField = entry.getKey(); 408 Map<TemporalField, Long> changes = targetField.resolve(this, entry.getValue()); 409 if (changes != null) { 410 resolveMakeChanges(data, targetField, changes); 411 data.fieldValues.remove(targetField); // helps avoid infinite loops 412 continue outer; // have to restart to avoid concurrent modification 413 } 414 } 415 break; 416 } 417 return this; 418 } 419 420 private void resolveMakeChanges(Parsed data, TemporalField targetField, Map<TemporalField, Long> changes) { 421 for (Map.Entry<TemporalField, Long> change : changes.entrySet()) { 422 TemporalField changeField = change.getKey(); 423 Long changeValue = change.getValue(); 424 Objects.requireNonNull(changeField, "changeField"); 425 if (changeValue != null) { 426 Long old = currentParsed().fieldValues.put(changeField, changeValue); 427 if (old != null && old.longValue() != changeValue.longValue()) { 428 throw new DateTimeException("Conflict found: " + changeField + " " + old + 429 " differs from " + changeField + " " + changeValue + 430 " while resolving " + targetField); 431 } 432 } else { 433 data.fieldValues.remove(changeField); 434 } 435 } 436 } 437 438 //----------------------------------------------------------------------- 439 // TemporalAccessor methods 440 // should only to be used once parsing is complete 441 @Override 442 public boolean isSupported(TemporalField field) { 443 if (currentParsed().fieldValues.containsKey(field)) { 444 return true; 445 } 446 return (field instanceof ChronoField == false) && field.isSupportedBy(this); 447 } 448 449 @Override 450 public long getLong(TemporalField field) { 451 Long value = currentParsed().fieldValues.get(field); 452 if (value != null) { 453 return value; 454 } 455 if (field instanceof ChronoField) { 456 throw new DateTimeException("Unsupported field: " + field.getName()); 457 } 458 return field.getFrom(this); 459 } 460 461 @SuppressWarnings("unchecked") 462 @Override 463 public <R> R query(TemporalQuery<R> query) { 464 if (query == Queries.chronology()) { 465 return (R) currentParsed().chrono; 466 } else if (query == Queries.zoneId()) { 467 return (R) currentParsed().zone; 468 } else if (query == Queries.precision()) { 469 return null; 470 } 471 return query.queryFrom(this); 472 } 473 474 //----------------------------------------------------------------------- 475 /** 476 * Returns a string version of the context for debugging. 477 * 478 * @return a string representation of the context data, not null 479 */ 480 @Override 481 public String toString() { 482 return currentParsed().toString(); 483 } 484 485 //----------------------------------------------------------------------- 486 /** 487 * Temporary store of parsed data. 488 */ 489 private static final class Parsed { 490 Chronology chrono = null; 491 ZoneId zone = null; 492 final Map<TemporalField, Long> fieldValues = new HashMap<>(); 493 private Parsed() { 494 } 495 protected Parsed copy() { 496 Parsed cloned = new Parsed(); 497 cloned.chrono = this.chrono; 498 cloned.zone = this.zone; 499 cloned.fieldValues.putAll(this.fieldValues); 500 return cloned; 501 } 502 @Override 503 public String toString() { 504 return fieldValues.toString() + "," + chrono + "," + zone; 505 } 506 } 507 508 } | 44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package java.time.format; 63 64 import java.time.ZoneId; 65 import java.time.chrono.Chronology; 66 import java.time.chrono.IsoChronology; 67 import java.time.temporal.TemporalField; 68 import java.util.ArrayList; 69 import java.util.Locale; 70 import java.util.Objects; 71 72 /** 73 * Context object used during date and time parsing. 74 * <p> 75 * This class represents the current state of the parse. 76 * It has the ability to store and retrieve the parsed values and manage optional segments. 77 * It also provides key information to the parsing methods. 78 * <p> 79 * Once parsing is complete, the {@link #toParsed()} is used to obtain the data. 80 * It contains a method to resolve the separate parsed fields into meaningful values. 81 * 82 * <h3>Specification for implementors</h3> 83 * This class is a mutable context intended for use from a single thread. 84 * Usage of the class is thread-safe within standard parsing as a new instance of this class 85 * is automatically created for each parse and parsing is single-threaded 86 * 87 * @since 1.8 88 */ 89 final class DateTimeParseContext { 90 91 /** 92 * The formatter, not null. 93 */ 94 private DateTimeFormatter formatter; 95 /** 96 * Whether to parse using case sensitively. 97 */ 98 private boolean caseSensitive = true; 99 /** 100 * Whether to parse using strict rules. 101 */ 102 private boolean strict = true; 103 /** 104 * The list of parsed data. 105 */ 106 private final ArrayList<Parsed> parsed = new ArrayList<>(); 107 108 /** 109 * Creates a new instance of the context. 282 * @param successful whether the optional segment was successfully parsed 283 */ 284 void endOptional(boolean successful) { 285 if (successful) { 286 parsed.remove(parsed.size() - 2); 287 } else { 288 parsed.remove(parsed.size() - 1); 289 } 290 } 291 292 //----------------------------------------------------------------------- 293 /** 294 * Gets the currently active temporal objects. 295 * 296 * @return the current temporal objects, not null 297 */ 298 private Parsed currentParsed() { 299 return parsed.get(parsed.size() - 1); 300 } 301 302 /** 303 * Gets the result of the parse. 304 * 305 * @return the result of the parse, not null 306 */ 307 Parsed toParsed() { 308 Parsed parsed = currentParsed(); 309 parsed.effectiveChrono = getEffectiveChronology(); 310 return parsed; 311 } 312 313 //----------------------------------------------------------------------- 314 /** 315 * Gets the first value that was parsed for the specified field. 316 * <p> 317 * This searches the results of the parse, returning the first value found 318 * for the specified field. No attempt is made to derive a value. 319 * The field may have an out of range value. 320 * For example, the day-of-month might be set to 50, or the hour to 1000. 321 * 322 * @param field the field to query from the map, null returns null 323 * @return the value mapped to the specified field, null if field was not parsed 324 */ 325 Long getParsed(TemporalField field) { 326 return currentParsed().fieldValues.get(field); 327 } 328 329 /** 330 * Stores the parsed field. 331 * <p> 332 * This stores a field-value pair that has been parsed. 355 void setParsed(Chronology chrono) { 356 Objects.requireNonNull(chrono, "chrono"); 357 currentParsed().chrono = chrono; 358 } 359 360 /** 361 * Stores the parsed zone. 362 * <p> 363 * This stores the zone that has been parsed. 364 * No validation is performed other than ensuring it is not null. 365 * 366 * @param zone the parsed zone, not null 367 */ 368 void setParsed(ZoneId zone) { 369 Objects.requireNonNull(zone, "zone"); 370 currentParsed().zone = zone; 371 } 372 373 //----------------------------------------------------------------------- 374 /** 375 * Returns a string version of the context for debugging. 376 * 377 * @return a string representation of the context data, not null 378 */ 379 @Override 380 public String toString() { 381 return currentParsed().toString(); 382 } 383 384 } |