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