Path: blob/master/src/java.base/share/classes/java/time/format/DateTimeParseContext.java
41159 views
/*1* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time.format;6263import java.time.ZoneId;64import java.time.chrono.Chronology;65import java.time.chrono.IsoChronology;66import java.time.temporal.TemporalAccessor;67import java.time.temporal.TemporalField;68import java.util.ArrayList;69import java.util.Locale;70import java.util.Objects;71import java.util.Set;72import java.util.function.Consumer;7374/**75* Context object used during date and time parsing.76* <p>77* This class represents the current state of the parse.78* It has the ability to store and retrieve the parsed values and manage optional segments.79* It also provides key information to the parsing methods.80* <p>81* Once parsing is complete, the {@link #toUnresolved()} is used to obtain the unresolved82* result data. The {@link #toResolved()} is used to obtain the resolved result.83*84* @implSpec85* This class is a mutable context intended for use from a single thread.86* Usage of the class is thread-safe within standard parsing as a new instance of this class87* is automatically created for each parse and parsing is single-threaded88*89* @since 1.890*/91final class DateTimeParseContext {9293/**94* The formatter, not null.95*/96private DateTimeFormatter formatter;97/**98* Whether to parse using case sensitively.99*/100private boolean caseSensitive = true;101/**102* Whether to parse using strict rules.103*/104private boolean strict = true;105/**106* The list of parsed data.107*/108private final ArrayList<Parsed> parsed = new ArrayList<>();109/**110* List of Consumers<Chronology> to be notified if the Chronology changes.111*/112private ArrayList<Consumer<Chronology>> chronoListeners = null;113114/**115* Creates a new instance of the context.116*117* @param formatter the formatter controlling the parse, not null118*/119DateTimeParseContext(DateTimeFormatter formatter) {120super();121this.formatter = formatter;122parsed.add(new Parsed());123}124125/**126* Creates a copy of this context.127* This retains the case sensitive and strict flags.128*/129DateTimeParseContext copy() {130DateTimeParseContext newContext = new DateTimeParseContext(formatter);131newContext.caseSensitive = caseSensitive;132newContext.strict = strict;133return newContext;134}135136//-----------------------------------------------------------------------137/**138* Gets the locale.139* <p>140* This locale is used to control localization in the parse except141* where localization is controlled by the DecimalStyle.142*143* @return the locale, not null144*/145Locale getLocale() {146return formatter.getLocale();147}148149/**150* Gets the DecimalStyle.151* <p>152* The DecimalStyle controls the numeric parsing.153*154* @return the DecimalStyle, not null155*/156DecimalStyle getDecimalStyle() {157return formatter.getDecimalStyle();158}159160/**161* Gets the effective chronology during parsing.162*163* @return the effective parsing chronology, not null164*/165Chronology getEffectiveChronology() {166Chronology chrono = currentParsed().chrono;167if (chrono == null) {168chrono = formatter.getChronology();169if (chrono == null) {170chrono = IsoChronology.INSTANCE;171}172}173return chrono;174}175176//-----------------------------------------------------------------------177/**178* Checks if parsing is case sensitive.179*180* @return true if parsing is case sensitive, false if case insensitive181*/182boolean isCaseSensitive() {183return caseSensitive;184}185186/**187* Sets whether the parsing is case sensitive or not.188*189* @param caseSensitive changes the parsing to be case sensitive or not from now on190*/191void setCaseSensitive(boolean caseSensitive) {192this.caseSensitive = caseSensitive;193}194195//-----------------------------------------------------------------------196/**197* Helper to compare two {@code CharSequence} instances.198* This uses {@link #isCaseSensitive()}.199*200* @param cs1 the first character sequence, not null201* @param offset1 the offset into the first sequence, valid202* @param cs2 the second character sequence, not null203* @param offset2 the offset into the second sequence, valid204* @param length the length to check, valid205* @return true if equal206*/207boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) {208if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) {209return false;210}211if (isCaseSensitive()) {212for (int i = 0; i < length; i++) {213char ch1 = cs1.charAt(offset1 + i);214char ch2 = cs2.charAt(offset2 + i);215if (ch1 != ch2) {216return false;217}218}219} else {220for (int i = 0; i < length; i++) {221char ch1 = cs1.charAt(offset1 + i);222char ch2 = cs2.charAt(offset2 + i);223if (ch1 != ch2 && Character.toUpperCase(ch1) != Character.toUpperCase(ch2) &&224Character.toLowerCase(ch1) != Character.toLowerCase(ch2)) {225return false;226}227}228}229return true;230}231232/**233* Helper to compare two {@code char}.234* This uses {@link #isCaseSensitive()}.235*236* @param ch1 the first character237* @param ch2 the second character238* @return true if equal239*/240boolean charEquals(char ch1, char ch2) {241if (isCaseSensitive()) {242return ch1 == ch2;243}244return charEqualsIgnoreCase(ch1, ch2);245}246247/**248* Compares two characters ignoring case.249*250* @param c1 the first251* @param c2 the second252* @return true if equal253*/254static boolean charEqualsIgnoreCase(char c1, char c2) {255return c1 == c2 ||256Character.toUpperCase(c1) == Character.toUpperCase(c2) ||257Character.toLowerCase(c1) == Character.toLowerCase(c2);258}259260//-----------------------------------------------------------------------261/**262* Checks if parsing is strict.263* <p>264* Strict parsing requires exact matching of the text and sign styles.265*266* @return true if parsing is strict, false if lenient267*/268boolean isStrict() {269return strict;270}271272/**273* Sets whether parsing is strict or lenient.274*275* @param strict changes the parsing to be strict or lenient from now on276*/277void setStrict(boolean strict) {278this.strict = strict;279}280281//-----------------------------------------------------------------------282/**283* Starts the parsing of an optional segment of the input.284*/285void startOptional() {286parsed.add(currentParsed().copy());287}288289/**290* Ends the parsing of an optional segment of the input.291*292* @param successful whether the optional segment was successfully parsed293*/294void endOptional(boolean successful) {295if (successful) {296parsed.remove(parsed.size() - 2);297} else {298parsed.remove(parsed.size() - 1);299}300}301302//-----------------------------------------------------------------------303/**304* Gets the currently active temporal objects.305*306* @return the current temporal objects, not null307*/308private Parsed currentParsed() {309return parsed.get(parsed.size() - 1);310}311312/**313* Gets the unresolved result of the parse.314*315* @return the result of the parse, not null316*/317Parsed toUnresolved() {318return currentParsed();319}320321/**322* Gets the resolved result of the parse.323*324* @return the result of the parse, not null325*/326TemporalAccessor toResolved(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {327Parsed parsed = currentParsed();328parsed.chrono = getEffectiveChronology();329parsed.zone = (parsed.zone != null ? parsed.zone : formatter.getZone());330return parsed.resolve(resolverStyle, resolverFields);331}332333334//-----------------------------------------------------------------------335/**336* Gets the first value that was parsed for the specified field.337* <p>338* This searches the results of the parse, returning the first value found339* for the specified field. No attempt is made to derive a value.340* The field may have an out of range value.341* For example, the day-of-month might be set to 50, or the hour to 1000.342*343* @param field the field to query from the map, null returns null344* @return the value mapped to the specified field, null if field was not parsed345*/346Long getParsed(TemporalField field) {347return currentParsed().fieldValues.get(field);348}349350/**351* Stores the parsed field.352* <p>353* This stores a field-value pair that has been parsed.354* The value stored may be out of range for the field - no checks are performed.355*356* @param field the field to set in the field-value map, not null357* @param value the value to set in the field-value map358* @param errorPos the position of the field being parsed359* @param successPos the position after the field being parsed360* @return the new position361*/362int setParsedField(TemporalField field, long value, int errorPos, int successPos) {363Objects.requireNonNull(field, "field");364Long old = currentParsed().fieldValues.put(field, value);365return (old != null && old.longValue() != value) ? ~errorPos : successPos;366}367368/**369* Stores the parsed chronology.370* <p>371* This stores the chronology that has been parsed.372* No validation is performed other than ensuring it is not null.373* <p>374* The list of listeners is copied and cleared so that each375* listener is called only once. A listener can add itself again376* if it needs to be notified of future changes.377*378* @param chrono the parsed chronology, not null379*/380void setParsed(Chronology chrono) {381Objects.requireNonNull(chrono, "chrono");382currentParsed().chrono = chrono;383if (chronoListeners != null && !chronoListeners.isEmpty()) {384@SuppressWarnings({"rawtypes", "unchecked"})385Consumer<Chronology>[] tmp = new Consumer[1];386Consumer<Chronology>[] listeners = chronoListeners.toArray(tmp);387chronoListeners.clear();388for (Consumer<Chronology> l : listeners) {389l.accept(chrono);390}391}392}393394/**395* Adds a Consumer<Chronology> to the list of listeners to be notified396* if the Chronology changes.397* @param listener a Consumer<Chronology> to be called when Chronology changes398*/399void addChronoChangedListener(Consumer<Chronology> listener) {400if (chronoListeners == null) {401chronoListeners = new ArrayList<>();402}403chronoListeners.add(listener);404}405406/**407* Stores the parsed zone.408* <p>409* This stores the zone that has been parsed.410* No validation is performed other than ensuring it is not null.411*412* @param zone the parsed zone, not null413*/414void setParsed(ZoneId zone) {415Objects.requireNonNull(zone, "zone");416currentParsed().zone = zone;417}418419/**420* Stores the parsed leap second.421*/422void setParsedLeapSecond() {423currentParsed().leapSecond = true;424}425426/**427* Stores the parsed day period.428*429* @param dayPeriod the parsed day period430*/431void setParsedDayPeriod(DateTimeFormatterBuilder.DayPeriod dayPeriod) {432currentParsed().dayPeriod = dayPeriod;433}434435//-----------------------------------------------------------------------436/**437* Returns a string version of the context for debugging.438*439* @return a string representation of the context data, not null440*/441@Override442public String toString() {443return currentParsed().toString();444}445446}447448449