src/share/classes/java/time/format/DateTimeParseContext.java

Print this page




  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 }