author | ameena |
Thu, 17 Nov 2016 11:55:59 +0000 | |
changeset 42160 | c229da92b1a9 |
parent 36638 | bc1438c48f1b |
permissions | -rw-r--r-- |
15289 | 1 |
/* |
42160
c229da92b1a9
8167618: DateTimeFormatter.format() uses exceptions for flow control
ameena
parents:
36638
diff
changeset
|
2 |
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. |
15289 | 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; |
|
16852 | 66 |
import static java.time.temporal.ChronoField.OFFSET_SECONDS; |
15289 | 67 |
|
68 |
import java.time.DateTimeException; |
|
69 |
import java.time.Instant; |
|
70 |
import java.time.ZoneId; |
|
16852 | 71 |
import java.time.ZoneOffset; |
72 |
import java.time.chrono.ChronoLocalDate; |
|
15658 | 73 |
import java.time.chrono.Chronology; |
16852 | 74 |
import java.time.chrono.IsoChronology; |
15289 | 75 |
import java.time.temporal.ChronoField; |
76 |
import java.time.temporal.TemporalAccessor; |
|
77 |
import java.time.temporal.TemporalField; |
|
20795 | 78 |
import java.time.temporal.TemporalQueries; |
15289 | 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> |
|
15658 | 87 |
* This class provides a single wrapper to items used in the format. |
15289 | 88 |
* |
17474 | 89 |
* @implSpec |
15289 | 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 |
|
15658 | 92 |
* a new instance of the class for each format and printing is single-threaded. |
15289 | 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 |
|
15658 | 115 |
* @param formatter the formatter controlling the format, not null |
15289 | 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) { |
|
16852 | 124 |
// normal case first (early return is an optimization) |
15658 | 125 |
Chronology overrideChrono = formatter.getChronology(); |
15289 | 126 |
ZoneId overrideZone = formatter.getZone(); |
127 |
if (overrideChrono == null && overrideZone == null) { |
|
128 |
return temporal; |
|
129 |
} |
|
130 |
||
16852 | 131 |
// ensure minimal change (early return is an optimization) |
20795 | 132 |
Chronology temporalChrono = temporal.query(TemporalQueries.chronology()); |
133 |
ZoneId temporalZone = temporal.query(TemporalQueries.zoneId()); |
|
16852 | 134 |
if (Objects.equals(overrideChrono, temporalChrono)) { |
15289 | 135 |
overrideChrono = null; |
136 |
} |
|
16852 | 137 |
if (Objects.equals(overrideZone, temporalZone)) { |
15289 | 138 |
overrideZone = null; |
139 |
} |
|
140 |
if (overrideChrono == null && overrideZone == null) { |
|
141 |
return temporal; |
|
142 |
} |
|
143 |
||
144 |
// make adjustment |
|
16852 | 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)) { |
|
33675
7d9d372a41df
8141652: Rename methods Objects.nonNullElse* to requireNonNullElse*
rriggs
parents:
25859
diff
changeset
|
149 |
Chronology chrono = Objects.requireNonNullElse(effectiveChrono, IsoChronology.INSTANCE); |
16852 | 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); |
|
19030 | 161 |
final ChronoLocalDate effectiveDate; |
16852 | 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); |
|
15289 | 173 |
} |
174 |
} |
|
175 |
} |
|
16852 | 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); |
|
15289 | 197 |
} |
16852 | 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); |
|
15289 | 204 |
} |
16852 | 205 |
return temporal.getLong(field); |
206 |
} |
|
207 |
@SuppressWarnings("unchecked") |
|
208 |
@Override |
|
209 |
public <R> R query(TemporalQuery<R> query) { |
|
20795 | 210 |
if (query == TemporalQueries.chronology()) { |
16852 | 211 |
return (R) effectiveChrono; |
212 |
} |
|
20795 | 213 |
if (query == TemporalQueries.zoneId()) { |
16852 | 214 |
return (R) effectiveZone; |
215 |
} |
|
20795 | 216 |
if (query == TemporalQueries.precision()) { |
16852 | 217 |
return temporal.query(query); |
218 |
} |
|
219 |
return query.queryFrom(this); |
|
220 |
} |
|
36638
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
221 |
|
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
222 |
@Override |
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
223 |
public String toString() { |
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
224 |
return temporal + |
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
225 |
(effectiveChrono != null ? " with chronology " + effectiveChrono : "") + |
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
226 |
(effectiveZone != null ? " with zone " + effectiveZone : ""); |
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
227 |
} |
16852 | 228 |
}; |
15289 | 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> |
|
15658 | 244 |
* This locale is used to control localization in the format output except |
17474 | 245 |
* where localization is controlled by the DecimalStyle. |
15289 | 246 |
* |
247 |
* @return the locale, not null |
|
248 |
*/ |
|
249 |
Locale getLocale() { |
|
250 |
return formatter.getLocale(); |
|
251 |
} |
|
252 |
||
253 |
/** |
|
17474 | 254 |
* Gets the DecimalStyle. |
15289 | 255 |
* <p> |
17474 | 256 |
* The DecimalStyle controls the localization of numeric output. |
15289 | 257 |
* |
17474 | 258 |
* @return the DecimalStyle, not null |
15289 | 259 |
*/ |
17474 | 260 |
DecimalStyle getDecimalStyle() { |
261 |
return formatter.getDecimalStyle(); |
|
15289 | 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) { |
|
36638
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
289 |
throw new DateTimeException("Unable to extract " + |
bc1438c48f1b
8085887: java.time.format.FormatStyle.LONG or FULL causes unchecked exception
rriggs
parents:
33675
diff
changeset
|
290 |
query + " from temporal " + temporal); |
15289 | 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) { |
|
42160
c229da92b1a9
8167618: DateTimeFormatter.format() uses exceptions for flow control
ameena
parents:
36638
diff
changeset
|
305 |
if (optional > 0 && !temporal.isSupported(field)) { |
c229da92b1a9
8167618: DateTimeFormatter.format() uses exceptions for flow control
ameena
parents:
36638
diff
changeset
|
306 |
return null; |
15289 | 307 |
} |
42160
c229da92b1a9
8167618: DateTimeFormatter.format() uses exceptions for flow control
ameena
parents:
36638
diff
changeset
|
308 |
return temporal.getLong(field); |
15289 | 309 |
} |
310 |
||
311 |
//----------------------------------------------------------------------- |
|
312 |
/** |
|
313 |
* Returns a string version of the context for debugging. |
|
314 |
* |
|
315 |
* @return a string representation of the context, not null |
|
316 |
*/ |
|
317 |
@Override |
|
318 |
public String toString() { |
|
319 |
return temporal.toString(); |
|
320 |
} |
|
321 |
||
322 |
} |