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 if (optional == 0) {
306 return temporal.getLong(field);
307 } else {
308 return temporal.isSupported(field) ? temporal.getLong(field) : null;
309 }
310 }
311
312 //-----------------------------------------------------------------------
313 /**
314 * Returns a string version of the context for debugging.
315 *
316 * @return a string representation of the context, not null
317 */
318 @Override
319 public String toString() {
320 return temporal.toString();
321 }
322
323 }