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.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.
 108      *
 109      * @param formatter  the formatter controlling the parse, not null
 110      */
 111     DateTimeParseContext(DateTimeFormatter formatter) {
 112         super();
 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 {
 241             parsed.remove(parsed.size() - 1);
 242         }
 243     }
 244 
 245     //-----------------------------------------------------------------------
 246     /**
 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 }