1 /*
   2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * This file is available under and governed by the GNU General Public
  28  * License version 2 only, as published by the Free Software Foundation.
  29  * However, the following notice accompanied the original version of this
  30  * file:
  31  *
  32  * Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
  33  *
  34  * All rights reserved.
  35  *
  36  * Redistribution and use in source and binary forms, with or without
  37  * modification, are permitted provided that the following conditions are met:
  38  *
  39  *  * Redistributions of source code must retain the above copyright notice,
  40  *    this list of conditions and the following disclaimer.
  41  *
  42  *  * Redistributions in binary form must reproduce the above copyright notice,
  43  *    this list of conditions and the following disclaimer in the documentation
  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.
 117      *
 118      * @param formatter  the formatter controlling the parse, not null
 119      */
 120     DateTimeParseContext(DateTimeFormatter formatter) {
 121         super();
 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 {
 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.
 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 }