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.TemporalAccessor; 68 import java.time.temporal.TemporalField; 69 import java.util.ArrayList; 70 import java.util.Locale; 71 import java.util.Objects; 72 import java.util.Set; 73 import 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 */ 92 final 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 */ DateTimeParseContext(DateTimeFormatter formatter)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 */ copy()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 */ getLocale()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 */ getDecimalStyle()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 */ getEffectiveChronology()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 */ isCaseSensitive()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 */ setCaseSensitive(boolean caseSensitive)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 */ subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length)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 */ charEquals(char ch1, char ch2)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 */ charEqualsIgnoreCase(char c1, char c2)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 */ isStrict()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 */ setStrict(boolean strict)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 */ startOptional()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 */ endOptional(boolean successful)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 */ currentParsed()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 */ toUnresolved()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 */ toResolved(ResolverStyle resolverStyle, Set<TemporalField> resolverFields)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 */ getParsed(TemporalField field)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 */ setParsedField(TemporalField field, long value, int errorPos, int successPos)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 */ setParsed(Chronology chrono)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 */ addChronoChangedListener(Consumer<Chronology> listener)400 void addChronoChangedListener(Consumer<Chronology> listener) { 401 if (chronoListeners == null) { 402 chronoListeners = new ArrayList<Consumer<Chronology>>(); 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 */ setParsed(ZoneId zone)415 void setParsed(ZoneId zone) { 416 Objects.requireNonNull(zone, "zone"); 417 currentParsed().zone = zone; 418 } 419 420 /** 421 * Stores the parsed leap second. 422 */ setParsedLeapSecond()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 toString()434 public String toString() { 435 return currentParsed().toString(); 436 } 437 438 } 439