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 */
62package java.time.format;
63
64import java.time.ZoneId;
65import java.time.chrono.Chronology;
66import java.time.chrono.IsoChronology;
67import java.time.temporal.TemporalAccessor;
68import java.time.temporal.TemporalField;
69import java.util.ArrayList;
70import java.util.Locale;
71import java.util.Objects;
72import java.util.Set;
73import java.util.function.Consumer;
74
75/**
76 * Context object used during date and time parsing.
77 * <p>
78 * This class represents the current state of the parse.
79 * It has the ability to store and retrieve the parsed values and manage optional segments.
80 * It also provides key information to the parsing methods.
81 * <p>
82 * Once parsing is complete, the {@link #toUnresolved()} is used to obtain the unresolved
83 * result data. The {@link #toResolved()} is used to obtain the resolved result.
84 *
85 * @implSpec
86 * This class is a mutable context intended for use from a single thread.
87 * Usage of the class is thread-safe within standard parsing as a new instance of this class
88 * is automatically created for each parse and parsing is single-threaded
89 *
90 * @since 1.8
91 */
92final class DateTimeParseContext {
93
94    /**
95     * The formatter, not null.
96     */
97    private DateTimeFormatter formatter;
98    /**
99     * Whether to parse using case sensitively.
100     */
101    private boolean caseSensitive = true;
102    /**
103     * Whether to parse using strict rules.
104     */
105    private boolean strict = true;
106    /**
107     * The list of parsed data.
108     */
109    private final ArrayList<Parsed> parsed = new ArrayList<>();
110    /**
111     * List of Consumers<Chronology> to be notified if the Chronology changes.
112     */
113    private ArrayList<Consumer<Chronology>> chronoListeners = null;
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     * This retains the case sensitive and strict flags.
129     */
130    DateTimeParseContext copy() {
131        DateTimeParseContext newContext = new DateTimeParseContext(formatter);
132        newContext.caseSensitive = caseSensitive;
133        newContext.strict = strict;
134        return newContext;
135    }
136
137    //-----------------------------------------------------------------------
138    /**
139     * Gets the locale.
140     * <p>
141     * This locale is used to control localization in the parse except
142     * where localization is controlled by the DecimalStyle.
143     *
144     * @return the locale, not null
145     */
146    Locale getLocale() {
147        return formatter.getLocale();
148    }
149
150    /**
151     * Gets the DecimalStyle.
152     * <p>
153     * The DecimalStyle controls the numeric parsing.
154     *
155     * @return the DecimalStyle, not null
156     */
157    DecimalStyle getDecimalStyle() {
158        return formatter.getDecimalStyle();
159    }
160
161    /**
162     * Gets the effective chronology during parsing.
163     *
164     * @return the effective parsing chronology, not null
165     */
166    Chronology getEffectiveChronology() {
167        Chronology chrono = currentParsed().chrono;
168        if (chrono == null) {
169            chrono = formatter.getChronology();
170            if (chrono == null) {
171                chrono = IsoChronology.INSTANCE;
172            }
173        }
174        return chrono;
175    }
176
177    //-----------------------------------------------------------------------
178    /**
179     * Checks if parsing is case sensitive.
180     *
181     * @return true if parsing is case sensitive, false if case insensitive
182     */
183    boolean isCaseSensitive() {
184        return caseSensitive;
185    }
186
187    /**
188     * Sets whether the parsing is case sensitive or not.
189     *
190     * @param caseSensitive  changes the parsing to be case sensitive or not from now on
191     */
192    void setCaseSensitive(boolean caseSensitive) {
193        this.caseSensitive = caseSensitive;
194    }
195
196    //-----------------------------------------------------------------------
197    /**
198     * Helper to compare two {@code CharSequence} instances.
199     * This uses {@link #isCaseSensitive()}.
200     *
201     * @param cs1  the first character sequence, not null
202     * @param offset1  the offset into the first sequence, valid
203     * @param cs2  the second character sequence, not null
204     * @param offset2  the offset into the second sequence, valid
205     * @param length  the length to check, valid
206     * @return true if equal
207     */
208    boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) {
209        if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) {
210            return false;
211        }
212        if (isCaseSensitive()) {
213            for (int i = 0; i < length; i++) {
214                char ch1 = cs1.charAt(offset1 + i);
215                char ch2 = cs2.charAt(offset2 + i);
216                if (ch1 != ch2) {
217                    return false;
218                }
219            }
220        } else {
221            for (int i = 0; i < length; i++) {
222                char ch1 = cs1.charAt(offset1 + i);
223                char ch2 = cs2.charAt(offset2 + i);
224                if (ch1 != ch2 && Character.toUpperCase(ch1) != Character.toUpperCase(ch2) &&
225                        Character.toLowerCase(ch1) != Character.toLowerCase(ch2)) {
226                    return false;
227                }
228            }
229        }
230        return true;
231    }
232
233    /**
234     * Helper to compare two {@code char}.
235     * This uses {@link #isCaseSensitive()}.
236     *
237     * @param ch1  the first character
238     * @param ch2  the second character
239     * @return true if equal
240     */
241    boolean charEquals(char ch1, char ch2) {
242        if (isCaseSensitive()) {
243            return ch1 == ch2;
244        }
245        return charEqualsIgnoreCase(ch1, ch2);
246    }
247
248    /**
249     * Compares two characters ignoring case.
250     *
251     * @param c1  the first
252     * @param c2  the second
253     * @return true if equal
254     */
255    static boolean charEqualsIgnoreCase(char c1, char c2) {
256        return c1 == c2 ||
257                Character.toUpperCase(c1) == Character.toUpperCase(c2) ||
258                Character.toLowerCase(c1) == Character.toLowerCase(c2);
259    }
260
261    //-----------------------------------------------------------------------
262    /**
263     * Checks if parsing is strict.
264     * <p>
265     * Strict parsing requires exact matching of the text and sign styles.
266     *
267     * @return true if parsing is strict, false if lenient
268     */
269    boolean isStrict() {
270        return strict;
271    }
272
273    /**
274     * Sets whether parsing is strict or lenient.
275     *
276     * @param strict  changes the parsing to be strict or lenient from now on
277     */
278    void setStrict(boolean strict) {
279        this.strict = strict;
280    }
281
282    //-----------------------------------------------------------------------
283    /**
284     * Starts the parsing of an optional segment of the input.
285     */
286    void startOptional() {
287        parsed.add(currentParsed().copy());
288    }
289
290    /**
291     * Ends the parsing of an optional segment of the input.
292     *
293     * @param successful  whether the optional segment was successfully parsed
294     */
295    void endOptional(boolean successful) {
296        if (successful) {
297            parsed.remove(parsed.size() - 2);
298        } else {
299            parsed.remove(parsed.size() - 1);
300        }
301    }
302
303    //-----------------------------------------------------------------------
304    /**
305     * Gets the currently active temporal objects.
306     *
307     * @return the current temporal objects, not null
308     */
309    private Parsed currentParsed() {
310        return parsed.get(parsed.size() - 1);
311    }
312
313    /**
314     * Gets the unresolved result of the parse.
315     *
316     * @return the result of the parse, not null
317     */
318    Parsed toUnresolved() {
319        return currentParsed();
320    }
321
322    /**
323     * Gets the resolved result of the parse.
324     *
325     * @return the result of the parse, not null
326     */
327    TemporalAccessor toResolved(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {
328        Parsed parsed = currentParsed();
329        parsed.chrono = getEffectiveChronology();
330        parsed.zone = (parsed.zone != null ? parsed.zone : formatter.getZone());
331        return parsed.resolve(resolverStyle, resolverFields);
332    }
333
334
335    //-----------------------------------------------------------------------
336    /**
337     * Gets the first value that was parsed for the specified field.
338     * <p>
339     * This searches the results of the parse, returning the first value found
340     * for the specified field. No attempt is made to derive a value.
341     * The field may have an out of range value.
342     * For example, the day-of-month might be set to 50, or the hour to 1000.
343     *
344     * @param field  the field to query from the map, null returns null
345     * @return the value mapped to the specified field, null if field was not parsed
346     */
347    Long getParsed(TemporalField field) {
348        return currentParsed().fieldValues.get(field);
349    }
350
351    /**
352     * Stores the parsed field.
353     * <p>
354     * This stores a field-value pair that has been parsed.
355     * The value stored may be out of range for the field - no checks are performed.
356     *
357     * @param field  the field to set in the field-value map, not null
358     * @param value  the value to set in the field-value map
359     * @param errorPos  the position of the field being parsed
360     * @param successPos  the position after the field being parsed
361     * @return the new position
362     */
363    int setParsedField(TemporalField field, long value, int errorPos, int successPos) {
364        Objects.requireNonNull(field, "field");
365        Long old = currentParsed().fieldValues.put(field, value);
366        return (old != null && old.longValue() != value) ? ~errorPos : successPos;
367    }
368
369    /**
370     * Stores the parsed chronology.
371     * <p>
372     * This stores the chronology that has been parsed.
373     * No validation is performed other than ensuring it is not null.
374     * <p>
375     * The list of listeners is copied and cleared so that each
376     * listener is called only once.  A listener can add itself again
377     * if it needs to be notified of future changes.
378     *
379     * @param chrono  the parsed chronology, not null
380     */
381    void setParsed(Chronology chrono) {
382        Objects.requireNonNull(chrono, "chrono");
383        currentParsed().chrono = chrono;
384        if (chronoListeners != null && !chronoListeners.isEmpty()) {
385            @SuppressWarnings({"rawtypes", "unchecked"})
386            Consumer<Chronology>[] tmp = new Consumer[1];
387            Consumer<Chronology>[] listeners = chronoListeners.toArray(tmp);
388            chronoListeners.clear();
389            for (Consumer<Chronology> l : listeners) {
390                l.accept(chrono);
391            }
392        }
393    }
394
395    /**
396     * Adds a Consumer<Chronology> to the list of listeners to be notified
397     * if the Chronology changes.
398     * @param listener a Consumer<Chronology> to be called when Chronology changes
399     */
400    void addChronoChangedListener(Consumer<Chronology> listener) {
401        if (chronoListeners == null) {
402            chronoListeners = new ArrayList<>();
403        }
404        chronoListeners.add(listener);
405    }
406
407    /**
408     * Stores the parsed zone.
409     * <p>
410     * This stores the zone that has been parsed.
411     * No validation is performed other than ensuring it is not null.
412     *
413     * @param zone  the parsed zone, not null
414     */
415    void setParsed(ZoneId zone) {
416        Objects.requireNonNull(zone, "zone");
417        currentParsed().zone = zone;
418    }
419
420    /**
421     * Stores the parsed leap second.
422     */
423    void setParsedLeapSecond() {
424        currentParsed().leapSecond = true;
425    }
426
427    //-----------------------------------------------------------------------
428    /**
429     * Returns a string version of the context for debugging.
430     *
431     * @return a string representation of the context data, not null
432     */
433    @Override
434    public String toString() {
435        return currentParsed().toString();
436    }
437
438}
439