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.temporal.TemporalField;

  65 import java.util.ArrayList;
  66 import java.util.List;
  67 import java.util.Locale;

  68 import java.util.Objects;
  69 
  70 /**
  71  * Context object used during date and time parsing.
  72  * <p>
  73  * This class represents the current state of the parse.
  74  * It has the ability to store and retrieve the parsed values and manage optional segments.
  75  * It also provides key information to the parsing methods.
  76  * <p>
  77  * Once parsing is complete, the {@link #toBuilder()} is typically used
  78  * to obtain a builder that can combine the separate parsed fields into meaningful values.
  79  *
  80  * <h3>Specification for implementors</h3>
  81  * This class is a mutable context intended for use from a single thread.
  82  * Usage of the class is thread-safe within standard parsing as a new instance of this class
  83  * is automatically created for each parse and parsing is single-threaded
  84  *
  85  * @since 1.8
  86  */
  87 final class DateTimeParseContext {
  88 
  89     /**
  90      * The formatter, not null.
  91      */
  92     private DateTimeFormatter formatter;
  93     /**
  94      * Whether to parse using case sensitively.
  95      */
  96     private boolean caseSensitive = true;
  97     /**
  98      * Whether to parse using strict rules.
  99      */
 100     private boolean strict = true;
 101     /**
 102      * The list of parsed data.
 103      */
 104     private final ArrayList<Parsed> parsed = new ArrayList<>();
 105 
 106     /**
 107      * Creates a new instance of the context.


 113         this.formatter = formatter;
 114         parsed.add(new Parsed());
 115     }
 116 
 117     /**
 118      * Creates a copy of this context.
 119      */
 120     DateTimeParseContext copy() {
 121         return new DateTimeParseContext(formatter);
 122     }
 123 
 124     //-----------------------------------------------------------------------
 125     /**
 126      * Gets the locale.
 127      * <p>
 128      * This locale is used to control localization in the parse except
 129      * where localization is controlled by the symbols.
 130      *
 131      * @return the locale, not null
 132      */
 133     public Locale getLocale() {
 134         return formatter.getLocale();
 135     }
 136 
 137     /**
 138      * Gets the formatting symbols.
 139      * <p>
 140      * The symbols control the localization of numeric parsing.
 141      *
 142      * @return the formatting symbols, not null
 143      */
 144     public DateTimeFormatSymbols getSymbols() {
 145         return formatter.getSymbols();
 146     }
 147 
















 148     //-----------------------------------------------------------------------
 149     /**
 150      * Checks if parsing is case sensitive.
 151      *
 152      * @return true if parsing is case sensitive, false if case insensitive
 153      */
 154     public boolean isCaseSensitive() {
 155         return caseSensitive;
 156     }
 157 
 158     /**
 159      * Sets whether the parsing is case sensitive or not.
 160      *
 161      * @param caseSensitive  changes the parsing to be case sensitive or not from now on
 162      */
 163     public void setCaseSensitive(boolean caseSensitive) {
 164         this.caseSensitive = caseSensitive;
 165     }
 166 

 167     /**
 168      * Helper to compare two {@code CharSequence} instances.
 169      * This uses {@link #isCaseSensitive()}.
 170      *
 171      * @param cs1  the first character sequence, not null
 172      * @param offset1  the offset into the first sequence, valid
 173      * @param cs2  the second character sequence, not null
 174      * @param offset2  the offset into the second sequence, valid
 175      * @param length  the length to check, valid
 176      * @return true if equal
 177      */
 178     public boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) {
 179         if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) {
 180             return false;
 181         }
 182         if (isCaseSensitive()) {
 183             for (int i = 0; i < length; i++) {
 184                 char ch1 = cs1.charAt(offset1 + i);
 185                 char ch2 = cs2.charAt(offset2 + i);
 186                 if (ch1 != ch2) {
 187                     return false;
 188                 }
 189             }
 190         } else {
 191             for (int i = 0; i < length; i++) {
 192                 char ch1 = cs1.charAt(offset1 + i);
 193                 char ch2 = cs2.charAt(offset2 + i);
 194                 if (ch1 != ch2 && Character.toUpperCase(ch1) != Character.toUpperCase(ch2) &&
 195                         Character.toLowerCase(ch1) != Character.toLowerCase(ch2)) {
 196                     return false;
 197                 }
 198             }
 199         }
 200         return true;
 201     }
 202 




























 203     //-----------------------------------------------------------------------
 204     /**
 205      * Checks if parsing is strict.
 206      * <p>
 207      * Strict parsing requires exact matching of the text and sign styles.
 208      *
 209      * @return true if parsing is strict, false if lenient
 210      */
 211     public boolean isStrict() {
 212         return strict;
 213     }
 214 
 215     /**
 216      * Sets whether parsing is strict or lenient.
 217      *
 218      * @param strict  changes the parsing to be strict or lenient from now on
 219      */
 220     public void setStrict(boolean strict) {
 221         this.strict = strict;
 222     }
 223 
 224     //-----------------------------------------------------------------------
 225     /**
 226      * Starts the parsing of an optional segment of the input.
 227      */
 228     void startOptional() {
 229         parsed.add(currentParsed().copy());
 230     }
 231 
 232     /**
 233      * Ends the parsing of an optional segment of the input.
 234      *
 235      * @param successful  whether the optional segment was successfully parsed
 236      */
 237     void endOptional(boolean successful) {
 238         if (successful) {
 239             parsed.remove(parsed.size() - 2);
 240         } else {


 247      * Gets the currently active temporal objects.
 248      *
 249      * @return the current temporal objects, not null
 250      */
 251     private Parsed currentParsed() {
 252         return parsed.get(parsed.size() - 1);
 253     }
 254 
 255     //-----------------------------------------------------------------------
 256     /**
 257      * Gets the first value that was parsed for the specified field.
 258      * <p>
 259      * This searches the results of the parse, returning the first value found
 260      * for the specified field. No attempt is made to derive a value.
 261      * The field may have an out of range value.
 262      * For example, the day-of-month might be set to 50, or the hour to 1000.
 263      *
 264      * @param field  the field to query from the map, null returns null
 265      * @return the value mapped to the specified field, null if field was not parsed
 266      */
 267     public Long getParsed(TemporalField field) {
 268         for (Object obj : currentParsed().parsed) {
 269             if (obj instanceof FieldValue) {
 270                 FieldValue fv = (FieldValue) obj;
 271                 if (fv.field.equals(field)) {
 272                     return fv.value;
 273                 }
 274             }
 275         }
 276         return null;
 277     }
 278 
 279     /**
 280      * Gets the first value that was parsed for the specified type.
 281      * <p>
 282      * This searches the results of the parse, returning the first date-time found
 283      * of the specified type. No attempt is made to derive a value.
 284      *
 285      * @param clazz  the type to query from the map, not null
 286      * @return the temporal object, null if it was not parsed
 287      */
 288     @SuppressWarnings("unchecked")
 289     public <T> T getParsed(Class<T> clazz) {
 290         for (Object obj : currentParsed().parsed) {
 291             if (clazz.isInstance(obj)) {
 292                 return (T) obj;
 293             }
 294         }
 295         return null;
 296     }
 297 
 298     /**
 299      * Gets the list of parsed temporal information.
 300      *
 301      * @return the list of parsed temporal objects, not null, no nulls
 302      */
 303     List<Object> getParsed() {
 304         // package scoped for testing
 305         return currentParsed().parsed;
 306     }
 307 
 308     /**
 309      * Stores the parsed field.
 310      * <p>
 311      * This stores a field-value pair that has been parsed.
 312      * The value stored may be out of range for the field - no checks are performed.
 313      *
 314      * @param field  the field to set in the field-value map, not null
 315      * @param value  the value to set in the field-value map



 316      */
 317     public void setParsedField(TemporalField field, long value) {
 318         Objects.requireNonNull(field, "field");
 319         currentParsed().parsed.add(new FieldValue(field, value));

 320     }
 321 
 322     /**
 323      * Stores the parsed complete object.
 324      * <p>
 325      * This stores a complete object that has been parsed.
 326      * No validation is performed on the date-time other than ensuring it is not null.
 327      *
 328      * @param object  the parsed object, not null
 329      */
 330     public <T> void setParsed(Object object) {
 331         Objects.requireNonNull(object, "object");
 332         currentParsed().parsed.add(object);













 333     }
 334 
 335     //-----------------------------------------------------------------------
 336     /**
 337      * Returns a {@code DateTimeBuilder} that can be used to interpret
 338      * the results of the parse.
 339      * <p>
 340      * This method is typically used once parsing is complete to obtain the parsed data.
 341      * Parsing will typically result in separate fields, such as year, month and day.
 342      * The returned builder can be used to combine the parsed data into meaningful
 343      * objects such as {@code LocalDate}, potentially applying complex processing
 344      * to handle invalid parsed data.
 345      *
 346      * @return a new builder with the results of the parse, not null
 347      */
 348     public DateTimeBuilder toBuilder() {
 349         List<Object> cals = currentParsed().parsed;
 350         DateTimeBuilder builder = new DateTimeBuilder();
 351         for (Object obj : cals) {
 352             if (obj instanceof FieldValue) {
 353                 FieldValue fv = (FieldValue) obj;
 354                 builder.addFieldValue(fv.field, fv.value);
 355             } else {
 356                 builder.addCalendrical(obj);
 357             }



 358         }
 359         return builder;
 360     }
 361 















































































 362     //-----------------------------------------------------------------------
 363     /**
 364      * Returns a string version of the context for debugging.
 365      *
 366      * @return a string representation of the context data, not null
 367      */
 368     @Override
 369     public String toString() {
 370         return currentParsed().toString();
 371     }
 372 
 373     //-----------------------------------------------------------------------
 374     /**
 375      * Temporary store of parsed data.
 376      */
 377     private static final class Parsed {
 378         final List<Object> parsed = new ArrayList<>();


 379         private Parsed() {
 380         }
 381         protected Parsed copy() {
 382             Parsed cloned = new Parsed();
 383             cloned.parsed.addAll(this.parsed);


 384             return cloned;
 385         }
 386         @Override
 387         public String toString() {
 388             return parsed.toString();
 389         }
 390     }
 391 
 392     //-----------------------------------------------------------------------
 393     /**
 394      * Temporary store of a field-value pair.
 395      */
 396     private static final class FieldValue {
 397         final TemporalField field;
 398         final long value;
 399         private FieldValue(TemporalField field, long value) {
 400             this.field = field;
 401             this.value = value;
 402         }
 403         @Override
 404         public String toString() {
 405             return field.getName() + ' ' + value;
 406         }
 407     }
 408 
 409 }


  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.


 122         this.formatter = formatter;
 123         parsed.add(new Parsed());
 124     }
 125 
 126     /**
 127      * Creates a copy of this context.
 128      */
 129     DateTimeParseContext copy() {
 130         return new DateTimeParseContext(formatter);
 131     }
 132 
 133     //-----------------------------------------------------------------------
 134     /**
 135      * Gets the locale.
 136      * <p>
 137      * This locale is used to control localization in the parse except
 138      * where localization is controlled by the symbols.
 139      *
 140      * @return the locale, not null
 141      */
 142     Locale getLocale() {
 143         return formatter.getLocale();
 144     }
 145 
 146     /**
 147      * Gets the formatting symbols.
 148      * <p>
 149      * The symbols control the localization of numeric parsing.
 150      *
 151      * @return the formatting symbols, not null
 152      */
 153     DateTimeFormatSymbols getSymbols() {
 154         return formatter.getSymbols();
 155     }
 156 
 157     /**
 158      * Gets the effective chronology during parsing.
 159      *
 160      * @return the effective parsing chronology, not null
 161      */
 162     Chronology getEffectiveChronology() {
 163         Chronology chrono = currentParsed().chrono;
 164         if (chrono == null) {
 165             chrono = formatter.getChronology();
 166             if (chrono == null) {
 167                 chrono = IsoChronology.INSTANCE;
 168             }
 169         }
 170         return chrono;
 171     }
 172 
 173     //-----------------------------------------------------------------------
 174     /**
 175      * Checks if parsing is case sensitive.
 176      *
 177      * @return true if parsing is case sensitive, false if case insensitive
 178      */
 179     boolean isCaseSensitive() {
 180         return caseSensitive;
 181     }
 182 
 183     /**
 184      * Sets whether the parsing is case sensitive or not.
 185      *
 186      * @param caseSensitive  changes the parsing to be case sensitive or not from now on
 187      */
 188     void setCaseSensitive(boolean caseSensitive) {
 189         this.caseSensitive = caseSensitive;
 190     }
 191 
 192     //-----------------------------------------------------------------------
 193     /**
 194      * Helper to compare two {@code CharSequence} instances.
 195      * This uses {@link #isCaseSensitive()}.
 196      *
 197      * @param cs1  the first character sequence, not null
 198      * @param offset1  the offset into the first sequence, valid
 199      * @param cs2  the second character sequence, not null
 200      * @param offset2  the offset into the second sequence, valid
 201      * @param length  the length to check, valid
 202      * @return true if equal
 203      */
 204     boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) {
 205         if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) {
 206             return false;
 207         }
 208         if (isCaseSensitive()) {
 209             for (int i = 0; i < length; i++) {
 210                 char ch1 = cs1.charAt(offset1 + i);
 211                 char ch2 = cs2.charAt(offset2 + i);
 212                 if (ch1 != ch2) {
 213                     return false;
 214                 }
 215             }
 216         } else {
 217             for (int i = 0; i < length; i++) {
 218                 char ch1 = cs1.charAt(offset1 + i);
 219                 char ch2 = cs2.charAt(offset2 + i);
 220                 if (ch1 != ch2 && Character.toUpperCase(ch1) != Character.toUpperCase(ch2) &&
 221                         Character.toLowerCase(ch1) != Character.toLowerCase(ch2)) {
 222                     return false;
 223                 }
 224             }
 225         }
 226         return true;
 227     }
 228 
 229     /**
 230      * Helper to compare two {@code char}.
 231      * This uses {@link #isCaseSensitive()}.
 232      *
 233      * @param ch1  the first character
 234      * @param ch2  the second character
 235      * @return true if equal
 236      */
 237     boolean charEquals(char ch1, char ch2) {
 238         if (isCaseSensitive()) {
 239             return ch1 == ch2;
 240         }
 241         return charEqualsIgnoreCase(ch1, ch2);
 242     }
 243 
 244     /**
 245      * Compares two characters ignoring case.
 246      *
 247      * @param c1  the first
 248      * @param c2  the second
 249      * @return true if equal
 250      */
 251     static boolean charEqualsIgnoreCase(char c1, char c2) {
 252         return c1 == c2 ||
 253                 Character.toUpperCase(c1) == Character.toUpperCase(c2) ||
 254                 Character.toLowerCase(c1) == Character.toLowerCase(c2);
 255     }
 256 
 257     //-----------------------------------------------------------------------
 258     /**
 259      * Checks if parsing is strict.
 260      * <p>
 261      * Strict parsing requires exact matching of the text and sign styles.
 262      *
 263      * @return true if parsing is strict, false if lenient
 264      */
 265     boolean isStrict() {
 266         return strict;
 267     }
 268 
 269     /**
 270      * Sets whether parsing is strict or lenient.
 271      *
 272      * @param strict  changes the parsing to be strict or lenient from now on
 273      */
 274     void setStrict(boolean strict) {
 275         this.strict = strict;
 276     }
 277 
 278     //-----------------------------------------------------------------------
 279     /**
 280      * Starts the parsing of an optional segment of the input.
 281      */
 282     void startOptional() {
 283         parsed.add(currentParsed().copy());
 284     }
 285 
 286     /**
 287      * Ends the parsing of an optional segment of the input.
 288      *
 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 {


 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.
 329      * The value stored may be out of range for the field - no checks are performed.
 330      *
 331      * @param field  the field to set in the field-value map, not null
 332      * @param value  the value to set in the field-value map
 333      * @param errorPos  the position of the field being parsed
 334      * @param successPos  the position after the field being parsed
 335      * @return the new position
 336      */
 337     int setParsedField(TemporalField field, long value, int errorPos, int successPos) {
 338         Objects.requireNonNull(field, "field");
 339         Long old = currentParsed().fieldValues.put(field, value);
 340         return (old != null && old.longValue() != value) ? ~errorPos : successPos;
 341     }
 342 
 343     /**
 344      * Stores the parsed chronology.
 345      * <p>
 346      * This stores the chronology that has been parsed.
 347      * No validation is performed other than ensuring it is not null.
 348      *
 349      * @param chrono  the parsed chronology, not null
 350      */
 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 }