View Javadoc
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 = (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 }