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-2013, 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 static java.time.temporal.ChronoField.AMPM_OF_DAY; 65 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; 66 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; 67 import static java.time.temporal.ChronoField.HOUR_OF_AMPM; 68 import static java.time.temporal.ChronoField.HOUR_OF_DAY; 69 import static java.time.temporal.ChronoField.MICRO_OF_DAY; 70 import static java.time.temporal.ChronoField.MICRO_OF_SECOND; 71 import static java.time.temporal.ChronoField.MILLI_OF_DAY; 72 import static java.time.temporal.ChronoField.MILLI_OF_SECOND; 73 import static java.time.temporal.ChronoField.MINUTE_OF_DAY; 74 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; 75 import static java.time.temporal.ChronoField.NANO_OF_DAY; 76 import static java.time.temporal.ChronoField.NANO_OF_SECOND; 77 import static java.time.temporal.ChronoField.SECOND_OF_DAY; 78 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; 79 80 import java.time.DateTimeException; 81 import java.time.LocalDate; 82 import java.time.LocalTime; 83 import java.time.ZoneId; 84 import java.time.chrono.ChronoLocalDate; 85 import java.time.chrono.Chronology; 86 import java.time.temporal.ChronoField; 87 import java.time.temporal.TemporalAccessor; 88 import java.time.temporal.TemporalField; 89 import java.time.temporal.TemporalQuery; 90 import java.time.temporal.UnsupportedTemporalTypeException; 91 import java.util.HashMap; 92 import java.util.Iterator; 93 import java.util.Map; 94 import java.util.Map.Entry; 95 import java.util.Objects; 96 import java.util.Set; 97 98 /** 99 * A store of parsed data. 100 * <p> 101 * This class is used during parsing to collect the data. Part of the parsing process 102 * involves handling optional blocks and multiple copies of the data get created to 103 * support the necessary backtracking. 104 * <p> 105 * Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}. 106 * In most cases, it is only exposed once the fields have been resolved. 107 * 108 * <h3>Specification for implementors</h3> 109 * This class is a mutable context intended for use from a single thread. 110 * Usage of the class is thread-safe within standard parsing as a new instance of this class 111 * is automatically created for each parse and parsing is single-threaded 112 * 113 * @since 1.8 114 */ 115 final class Parsed implements TemporalAccessor { 116 // some fields are accessed using package scope from DateTimeParseContext 117 118 /** 119 * The parsed fields. 120 */ 121 final Map<TemporalField, Long> fieldValues = new HashMap<>(); 122 /** 123 * The parsed zone. 124 */ 125 ZoneId zone; 126 /** 127 * The parsed chronology. 128 */ 129 Chronology chrono; 130 /** 131 * The effective chronology. 132 */ 133 Chronology effectiveChrono; 134 /** 135 * The resolver style to use. 136 */ 137 private ResolverStyle resolverStyle; 138 /** 139 * The resolved date. 140 */ 141 private ChronoLocalDate<?> date; 142 /** 143 * The resolved time. 144 */ 145 private LocalTime time; 146 147 /** 148 * Creates an instance. 149 */ 150 Parsed() { 151 } 152 153 /** 154 * Creates a copy. 155 */ 156 Parsed copy() { 157 // only copy fields used in parsing stage 158 Parsed cloned = new Parsed(); 159 cloned.fieldValues.putAll(this.fieldValues); 160 cloned.zone = this.zone; 161 cloned.chrono = this.chrono; 162 return cloned; 163 } 164 165 //----------------------------------------------------------------------- 166 @Override 167 public boolean isSupported(TemporalField field) { 168 if (fieldValues.containsKey(field) || 169 (date != null && date.isSupported(field)) || 170 (time != null && time.isSupported(field))) { 171 return true; 172 } 173 return field != null && (field instanceof ChronoField == false) && field.isSupportedBy(this); 174 } 175 176 @Override 177 public long getLong(TemporalField field) { 178 Objects.requireNonNull(field, "field"); 179 Long value = fieldValues.get(field); 180 if (value != null) { 181 return value; 182 } 183 if (date != null && date.isSupported(field)) { 184 return date.getLong(field); 185 } 186 if (time != null && time.isSupported(field)) { 187 return time.getLong(field); 188 } 189 if (field instanceof ChronoField) { 190 throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); 191 } 192 return field.getFrom(this); 193 } 194 195 @SuppressWarnings("unchecked") 196 @Override 197 public <R> R query(TemporalQuery<R> query) { 198 if (query == TemporalQuery.zoneId()) { 199 return (R) zone; 200 } else if (query == TemporalQuery.chronology()) { 201 return (R) chrono; 202 } else if (query == TemporalQuery.localDate()) { 203 return (R) (date != null ? LocalDate.from(date) : null); 204 } else if (query == TemporalQuery.localTime()) { 205 return (R) time; 206 } else if (query == TemporalQuery.zone() || query == TemporalQuery.offset()) { 207 return query.queryFrom(this); 208 } else if (query == TemporalQuery.precision()) { 209 return null; // not a complete date/time 210 } 211 // inline TemporalAccessor.super.query(query) as an optimization 212 // non-JDK classes are not permitted to make this optimization 213 return query.queryFrom(this); 214 } 215 216 //----------------------------------------------------------------------- 217 /** 218 * Resolves the fields in this context. 219 * 220 * @param resolverStyle the resolver style, not null 221 * @param resolverFields the fields to use for resolving, null for all fields 222 * @return this, for method chaining 223 * @throws DateTimeException if resolving one field results in a value for 224 * another field that is in conflict 225 */ 226 TemporalAccessor resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) { 227 if (resolverFields != null) { 228 fieldValues.keySet().retainAll(resolverFields); 229 } 230 this.resolverStyle = resolverStyle; 231 chrono = effectiveChrono; 232 resolveFields(); 233 resolveTimeLenient(); 234 crossCheck(); 235 return this; 236 } 237 238 //----------------------------------------------------------------------- 239 private void resolveFields() { 240 // resolve ChronoField 241 resolveDateFields(); 242 resolveTimeFields(); 243 244 // if any other fields, handle them 245 // any lenient date resolution should return epoch-day 246 if (fieldValues.size() > 0) { 247 boolean changed = false; 248 outer: 249 while (true) { 250 for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) { 251 TemporalField targetField = entry.getKey(); 252 Map<TemporalField, Long> changes = targetField.resolve(this, entry.getValue(), resolverStyle); 253 if (changes != null) { 254 changed = true; 255 resolveFieldsMakeChanges(targetField, changes); 256 fieldValues.remove(targetField); // helps avoid infinite loops 257 continue outer; // have to restart to avoid concurrent modification 258 } 259 } 260 break; 261 } 262 // if something changed then have to redo ChronoField resolve 263 if (changed) { 264 resolveDateFields(); 265 resolveTimeFields(); 266 } 267 } 268 } 269 270 private void resolveFieldsMakeChanges(TemporalField targetField, Map<TemporalField, Long> changes) { 271 for (Map.Entry<TemporalField, Long> change : changes.entrySet()) { 272 TemporalField changeField = change.getKey(); 273 Long changeValue = change.getValue(); 274 Objects.requireNonNull(changeField, "changeField"); 275 if (changeValue != null) { 276 updateCheckConflict(targetField, changeField, changeValue); 277 } else { 278 fieldValues.remove(changeField); 279 } 280 } 281 } 282 283 private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) { 284 Long old = fieldValues.put(changeField, changeValue); 285 if (old != null && old.longValue() != changeValue.longValue()) { 286 throw new DateTimeException("Conflict found: " + changeField + " " + old + 287 " differs from " + changeField + " " + changeValue + 288 " while resolving " + targetField); 289 } 290 } 291 292 //----------------------------------------------------------------------- 293 private void resolveDateFields() { 294 updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle)); 295 } 296 297 private void updateCheckConflict(ChronoLocalDate<?> cld) { 298 if (date != null) { 299 if (cld != null && date.equals(cld) == false) { 300 throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld); 301 } 302 } else { 303 date = cld; 304 } 305 } 306 307 //----------------------------------------------------------------------- 308 private void resolveTimeFields() { 309 // simplify fields 310 if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) { 311 long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY); 312 updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch); 313 } 314 if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) { 315 long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM); 316 updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch); 317 } 318 if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) { 319 long ap = fieldValues.remove(AMPM_OF_DAY); 320 long hap = fieldValues.remove(HOUR_OF_AMPM); 321 updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap); 322 } 323 if (fieldValues.containsKey(MICRO_OF_DAY)) { 324 long cod = fieldValues.remove(MICRO_OF_DAY); 325 updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L); 326 updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L); 327 } 328 if (fieldValues.containsKey(MILLI_OF_DAY)) { 329 long lod = fieldValues.remove(MILLI_OF_DAY); 330 updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000); 331 updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000); 332 } 333 if (fieldValues.containsKey(SECOND_OF_DAY)) { 334 long sod = fieldValues.remove(SECOND_OF_DAY); 335 updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600); 336 updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60); 337 updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60); 338 } 339 if (fieldValues.containsKey(MINUTE_OF_DAY)) { 340 long mod = fieldValues.remove(MINUTE_OF_DAY); 341 updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60); 342 updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60); 343 } 344 345 // combine partial second fields strictly, leaving lenient expansion to later 346 if (fieldValues.containsKey(NANO_OF_SECOND)) { 347 long nos = fieldValues.get(NANO_OF_SECOND); 348 if (fieldValues.containsKey(MICRO_OF_SECOND)) { 349 long cos = fieldValues.remove(MICRO_OF_SECOND); 350 nos = cos * 1000 + (nos % 1000); 351 updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos); 352 } 353 if (fieldValues.containsKey(MILLI_OF_SECOND)) { 354 long los = fieldValues.remove(MILLI_OF_SECOND); 355 updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L)); 356 } 357 } 358 359 // convert to time if possible 360 if (fieldValues.containsKey(NANO_OF_DAY)) { 361 long nod = fieldValues.remove(NANO_OF_DAY); 362 updateCheckConflict(LocalTime.ofNanoOfDay(nod)); 363 } 364 if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) && 365 fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) { 366 int hodVal = HOUR_OF_DAY.checkValidIntValue(fieldValues.remove(HOUR_OF_DAY)); 367 int mohVal = MINUTE_OF_HOUR.checkValidIntValue(fieldValues.remove(MINUTE_OF_HOUR)); 368 int somVal = SECOND_OF_MINUTE.checkValidIntValue(fieldValues.remove(SECOND_OF_MINUTE)); 369 int nosVal = NANO_OF_SECOND.checkValidIntValue(fieldValues.remove(NANO_OF_SECOND)); 370 updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal)); 371 } 372 } 373 374 private void resolveTimeLenient() { 375 // leniently create a time from incomplete information 376 // done after everything else as it creates information from nothing 377 // which would break updateCheckConflict(field) 378 379 if (time == null) { 380 // can only get here if NANO_OF_SECOND not present 381 if (fieldValues.containsKey(MILLI_OF_SECOND)) { 382 long los = fieldValues.remove(MILLI_OF_SECOND); 383 if (fieldValues.containsKey(MICRO_OF_SECOND)) { 384 // merge milli-of-second and micro-of-second for better error message 385 long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000); 386 updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos); 387 fieldValues.remove(MICRO_OF_SECOND); 388 fieldValues.put(NANO_OF_SECOND, cos * 1_000L); 389 } else { 390 // convert milli-of-second to nano-of-second 391 fieldValues.put(NANO_OF_SECOND, los * 1_000_000L); 392 } 393 } else if (fieldValues.containsKey(MICRO_OF_SECOND)) { 394 // convert micro-of-second to nano-of-second 395 long cos = fieldValues.remove(MICRO_OF_SECOND); 396 fieldValues.put(NANO_OF_SECOND, cos * 1_000L); 397 } 398 } 399 400 // merge hour/minute/second/nano leniently 401 Long hod = fieldValues.get(HOUR_OF_DAY); 402 if (hod != null) { 403 int hodVal = HOUR_OF_DAY.checkValidIntValue(hod); 404 Long moh = fieldValues.get(MINUTE_OF_HOUR); 405 Long som = fieldValues.get(SECOND_OF_MINUTE); 406 Long nos = fieldValues.get(NANO_OF_SECOND); 407 408 // check for invalid combinations that cannot be defaulted 409 if (time == null) { 410 if ((moh == null && (som != null || nos != null)) || 411 (moh != null && som == null && nos != null)) { 412 return; 413 } 414 } 415 416 // default as necessary and build time 417 int mohVal = (moh != null ? MINUTE_OF_HOUR.checkValidIntValue(moh) : (time != null ? time.getMinute() : 0)); 418 int somVal = (som != null ? SECOND_OF_MINUTE.checkValidIntValue(som) : (time != null ? time.getSecond() : 0)); 419 int nosVal = (nos != null ? NANO_OF_SECOND.checkValidIntValue(nos) : (time != null ? time.getNano() : 0)); 420 updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal)); 421 fieldValues.remove(HOUR_OF_DAY); 422 fieldValues.remove(MINUTE_OF_HOUR); 423 fieldValues.remove(SECOND_OF_MINUTE); 424 fieldValues.remove(NANO_OF_SECOND); 425 } 426 } 427 428 private void updateCheckConflict(LocalTime lt) { 429 if (time != null) { 430 if (lt != null && time.equals(lt) == false) { 431 throw new DateTimeException("Conflict found: Fields resolved to two different times: " + time + " " + lt); 432 } 433 } else { 434 time = lt; 435 } 436 } 437 438 //----------------------------------------------------------------------- 439 private void crossCheck() { 440 // only cross-check date, time and date-time 441 // avoid object creation if possible 442 if (date != null) { 443 crossCheck(date); 444 } 445 if (time != null) { 446 crossCheck(time); 447 if (date != null && fieldValues.size() > 0) { 448 crossCheck(date.atTime(time)); 449 } 450 } 451 } 452 453 private void crossCheck(TemporalAccessor target) { 454 for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) { 455 Entry<TemporalField, Long> entry = it.next(); 456 TemporalField field = entry.getKey(); 457 long val1; 458 try { 459 val1 = target.getLong(field); 460 } catch (RuntimeException ex) { 461 continue; 462 } 463 long val2 = entry.getValue(); 464 if (val1 != val2) { 465 throw new DateTimeException("Conflict found: Field " + field + " " + val1 + 466 " differs from " + field + " " + val2 + " derived from " + target); 467 } 468 it.remove(); 469 } 470 } 471 472 //----------------------------------------------------------------------- 473 @Override 474 public String toString() { 475 String str = fieldValues.toString() + "," + chrono + "," + zone; 476 if (date != null || time != null) { 477 str += " resolved to " + date + "," + time; 478 } 479 return str; 480 } 481 482 }