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 }