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      */
DateTimePrintContext(TemporalAccessor temporal, DateTimeFormatter formatter)117     DateTimePrintContext(TemporalAccessor temporal, DateTimeFormatter formatter) {
118         super();
119         this.temporal = adjust(temporal, formatter);
120         this.formatter = formatter;
121     }
122 
adjust(final TemporalAccessor temporal, DateTimeFormatter formatter)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 = (effectiveChrono != null ? 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     }
223 
224     //-----------------------------------------------------------------------
225     /**
226      * Gets the temporal object being output.
227      *
228      * @return the temporal object, not null
229      */
230     TemporalAccessor getTemporal() {
231         return temporal;
232     }
233 
234     /**
235      * Gets the locale.
236      * <p>
237      * This locale is used to control localization in the format output except
238      * where localization is controlled by the DecimalStyle.
239      *
240      * @return the locale, not null
241      */
242     Locale getLocale() {
243         return formatter.getLocale();
244     }
245 
246     /**
247      * Gets the DecimalStyle.
248      * <p>
249      * The DecimalStyle controls the localization of numeric output.
250      *
251      * @return the DecimalStyle, not null
252      */
253     DecimalStyle getDecimalStyle() {
254         return formatter.getDecimalStyle();
255     }
256 
257     //-----------------------------------------------------------------------
258     /**
259      * Starts the printing of an optional segment of the input.
260      */
261     void startOptional() {
262         this.optional++;
263     }
264 
265     /**
266      * Ends the printing of an optional segment of the input.
267      */
268     void endOptional() {
269         this.optional--;
270     }
271 
272     /**
273      * Gets a value using a query.
274      *
275      * @param query  the query to use, not null
276      * @return the result, null if not found and optional is true
277      * @throws DateTimeException if the type is not available and the section is not optional
278      */
279     <R> R getValue(TemporalQuery<R> query) {
280         R result = temporal.query(query);
281         if (result == null && optional == 0) {
282             throw new DateTimeException("Unable to extract value: " + temporal.getClass());
283         }
284         return result;
285     }
286 
287     /**
288      * Gets the value of the specified field.
289      * <p>
290      * This will return the value for the specified field.
291      *
292      * @param field  the field to find, not null
293      * @return the value, null if not found and optional is true
294      * @throws DateTimeException if the field is not available and the section is not optional
295      */
296     Long getValue(TemporalField field) {
297         try {
298             return temporal.getLong(field);
299         } catch (DateTimeException ex) {
300             if (optional > 0) {
301                 return null;
302             }
303             throw ex;
304         }
305     }
306 
307     //-----------------------------------------------------------------------
308     /**
309      * Returns a string version of the context for debugging.
310      *
311      * @return a string representation of the context, not null
312      */
313     @Override
314     public String toString() {
315         return temporal.toString();
316     }
317 
318 }
319