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) 2011-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 static java.time.temporal.ChronoField.EPOCH_DAY;
  65 import static java.time.temporal.ChronoField.INSTANT_SECONDS;
  66 import static java.time.temporal.ChronoField.OFFSET_SECONDS;
  67 
  68 import java.time.DateTimeException;
  69 import java.time.Instant;
  70 import java.time.ZoneId;
  71 import java.time.ZoneOffset;
  72 import java.time.chrono.ChronoLocalDate;
  73 import java.time.chrono.Chronology;
  74 import java.time.chrono.IsoChronology;
  75 import java.time.temporal.ChronoField;
  76 import java.time.temporal.TemporalAccessor;
  77 import java.time.temporal.TemporalField;
  78 import java.time.temporal.TemporalQueries;
  79 import java.time.temporal.TemporalQuery;
  80 import java.time.temporal.ValueRange;
  81 import java.util.Locale;
  82 import java.util.Objects;
  83 
  84 /**
  85  * Context object used during date and time printing.
  86  * <p>
  87  * This class provides a single wrapper to items used in the format.
  88  *
  89  * @implSpec
  90  * This class is a mutable context intended for use from a single thread.
  91  * Usage of the class is thread-safe within standard printing as the framework creates
  92  * a new instance of the class for each format and printing is single-threaded.
  93  *
  94  * @since 1.8
  95  */
  96 final class DateTimePrintContext {
  97 
  98     /**
  99      * The temporal being output.
 100      */
 101     private TemporalAccessor temporal;
 102     /**
 103      * The formatter, not null.
 104      */
 105     private DateTimeFormatter formatter;
 106     /**
 107      * Whether the current formatter is optional.
 108      */
 109     private int optional;
 110 
 111     /**
 112      * Creates a new instance of the context.
 113      *
 114      * @param temporal  the temporal object being output, not null
 115      * @param formatter  the formatter controlling the format, not null
 116      */
 117     DateTimePrintContext(TemporalAccessor temporal, DateTimeFormatter formatter) {
 118         super();
 119         this.temporal = adjust(temporal, formatter);
 120         this.formatter = formatter;
 121     }
 122 
 123     private static TemporalAccessor adjust(final TemporalAccessor temporal, DateTimeFormatter formatter) {
 124         // normal case first (early return is an optimization)
 125         Chronology overrideChrono = formatter.getChronology();
 126         ZoneId overrideZone = formatter.getZone();
 127         if (overrideChrono == null && overrideZone == null) {
 128             return temporal;
 129         }
 130 
 131         // ensure minimal change (early return is an optimization)
 132         Chronology temporalChrono = temporal.query(TemporalQueries.chronology());
 133         ZoneId temporalZone = temporal.query(TemporalQueries.zoneId());
 134         if (Objects.equals(overrideChrono, temporalChrono)) {
 135             overrideChrono = null;
 136         }
 137         if (Objects.equals(overrideZone, temporalZone)) {
 138             overrideZone = null;
 139         }
 140         if (overrideChrono == null && overrideZone == null) {
 141             return temporal;
 142         }
 143 
 144         // make adjustment
 145         final Chronology effectiveChrono = (overrideChrono != null ? overrideChrono : temporalChrono);
 146         if (overrideZone != null) {
 147             // if have zone and instant, calculation is simple, defaulting chrono if necessary
 148             if (temporal.isSupported(INSTANT_SECONDS)) {
 149                 Chronology chrono = Objects.requireNonNullElse(effectiveChrono, IsoChronology.INSTANCE);
 150                 return chrono.zonedDateTime(Instant.from(temporal), overrideZone);
 151             }
 152             // block changing zone on OffsetTime, and similar problem cases
 153             if (overrideZone.normalized() instanceof ZoneOffset && temporal.isSupported(OFFSET_SECONDS) &&
 154                     temporal.get(OFFSET_SECONDS) != overrideZone.getRules().getOffset(Instant.EPOCH).getTotalSeconds()) {
 155                 throw new DateTimeException("Unable to apply override zone '" + overrideZone +
 156                         "' because the temporal object being formatted has a different offset but" +
 157                         " does not represent an instant: " + temporal);
 158             }
 159         }
 160         final ZoneId effectiveZone = (overrideZone != null ? overrideZone : temporalZone);
 161         final ChronoLocalDate effectiveDate;
 162         if (overrideChrono != null) {
 163             if (temporal.isSupported(EPOCH_DAY)) {
 164                 effectiveDate = effectiveChrono.date(temporal);
 165             } else {
 166                 // check for date fields other than epoch-day, ignoring case of converting null to ISO
 167                 if (!(overrideChrono == IsoChronology.INSTANCE && temporalChrono == null)) {
 168                     for (ChronoField f : ChronoField.values()) {
 169                         if (f.isDateBased() && temporal.isSupported(f)) {
 170                             throw new DateTimeException("Unable to apply override chronology '" + overrideChrono +
 171                                     "' because the temporal object being formatted contains date fields but" +
 172                                     " does not represent a whole date: " + temporal);
 173                         }
 174                     }
 175                 }
 176                 effectiveDate = null;
 177             }
 178         } else {
 179             effectiveDate = null;
 180         }
 181 
 182         // combine available data
 183         // this is a non-standard temporal that is almost a pure delegate
 184         // this better handles map-like underlying temporal instances
 185         return new TemporalAccessor() {
 186             @Override
 187             public boolean isSupported(TemporalField field) {
 188                 if (effectiveDate != null && field.isDateBased()) {
 189                     return effectiveDate.isSupported(field);
 190                 }
 191                 return temporal.isSupported(field);
 192             }
 193             @Override
 194             public ValueRange range(TemporalField field) {
 195                 if (effectiveDate != null && field.isDateBased()) {
 196                     return effectiveDate.range(field);
 197                 }
 198                 return temporal.range(field);
 199             }
 200             @Override
 201             public long getLong(TemporalField field) {
 202                 if (effectiveDate != null && field.isDateBased()) {
 203                     return effectiveDate.getLong(field);
 204                 }
 205                 return temporal.getLong(field);
 206             }
 207             @SuppressWarnings("unchecked")
 208             @Override
 209             public <R> R query(TemporalQuery<R> query) {
 210                 if (query == TemporalQueries.chronology()) {
 211                     return (R) effectiveChrono;
 212                 }
 213                 if (query == TemporalQueries.zoneId()) {
 214                     return (R) effectiveZone;
 215                 }
 216                 if (query == TemporalQueries.precision()) {
 217                     return temporal.query(query);
 218                 }
 219                 return query.queryFrom(this);
 220             }
 221 
 222             @Override
 223             public String toString() {
 224                 return temporal +
 225                         (effectiveChrono != null ? " with chronology " + effectiveChrono : "") +
 226                         (effectiveZone != null ? " with zone " + effectiveZone : "");
 227             }
 228         };
 229     }
 230 
 231     //-----------------------------------------------------------------------
 232     /**
 233      * Gets the temporal object being output.
 234      *
 235      * @return the temporal object, not null
 236      */
 237     TemporalAccessor getTemporal() {
 238         return temporal;
 239     }
 240 
 241     /**
 242      * Gets the locale.
 243      * <p>
 244      * This locale is used to control localization in the format output except
 245      * where localization is controlled by the DecimalStyle.
 246      *
 247      * @return the locale, not null
 248      */
 249     Locale getLocale() {
 250         return formatter.getLocale();
 251     }
 252 
 253     /**
 254      * Gets the DecimalStyle.
 255      * <p>
 256      * The DecimalStyle controls the localization of numeric output.
 257      *
 258      * @return the DecimalStyle, not null
 259      */
 260     DecimalStyle getDecimalStyle() {
 261         return formatter.getDecimalStyle();
 262     }
 263 
 264     //-----------------------------------------------------------------------
 265     /**
 266      * Starts the printing of an optional segment of the input.
 267      */
 268     void startOptional() {
 269         this.optional++;
 270     }
 271 
 272     /**
 273      * Ends the printing of an optional segment of the input.
 274      */
 275     void endOptional() {
 276         this.optional--;
 277     }
 278 
 279     /**
 280      * Gets a value using a query.
 281      *
 282      * @param query  the query to use, not null
 283      * @return the result, null if not found and optional is true
 284      * @throws DateTimeException if the type is not available and the section is not optional
 285      */
 286     <R> R getValue(TemporalQuery<R> query) {
 287         R result = temporal.query(query);
 288         if (result == null && optional == 0) {
 289             throw new DateTimeException("Unable to extract " +
 290                     query + " from temporal " + temporal);
 291         }
 292         return result;
 293     }
 294 
 295     /**
 296      * Gets the value of the specified field.
 297      * <p>
 298      * This will return the value for the specified field.
 299      *
 300      * @param field  the field to find, not null
 301      * @return the value, null if not found and optional is true
 302      * @throws DateTimeException if the field is not available and the section is not optional
 303      */
 304     Long getValue(TemporalField field) {
 305         try {
 306             return temporal.getLong(field);
 307         } catch (DateTimeException ex) {
 308             if (optional > 0) {
 309                 return null;
 310             }
 311             throw ex;
 312         }
 313     }
 314 
 315     //-----------------------------------------------------------------------
 316     /**
 317      * Returns a string version of the context for debugging.
 318      *
 319      * @return a string representation of the context, not null
 320      */
 321     @Override
 322     public String toString() {
 323         return temporal.toString();
 324     }
 325 
 326 }