View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.time;
18  
19  import java.text.DateFormat;
20  import java.text.Format;
21  import java.text.SimpleDateFormat;
22  import java.util.Arrays;
23  import java.util.Locale;
24  import java.util.Objects;
25  import java.util.TimeZone;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.ConcurrentMap;
28  
29  import org.apache.commons.lang3.LocaleUtils;
30  
31  /**
32   * FormatCache is a cache and factory for {@link Format}s.
33   *
34   * @param <F> The Format type.
35   *
36   * @since 3.0
37   */
38  // TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach.
39  abstract class AbstractFormatCache<F extends Format> {
40  
41      /**
42       * Helper class to hold multipart Map keys as arrays.
43       */
44      private static final class ArrayKey {
45  
46          private static int computeHashCode(final Object[] keys) {
47              final int prime = 31;
48              int result = 1;
49              result = prime * result + Arrays.hashCode(keys);
50              return result;
51          }
52  
53          private final Object[] keys;
54          private final int hashCode;
55  
56          /**
57           * Constructs an instance of {@link MultipartKey} to hold the specified objects.
58           *
59           * @param keys the set of objects that make up the key.  Each key may be null.
60           */
61          ArrayKey(final Object... keys) {
62              this.keys = keys;
63              this.hashCode = computeHashCode(keys);
64          }
65  
66          @Override
67          public boolean equals(final Object obj) {
68              if (this == obj) {
69                  return true;
70              }
71              if (obj == null) {
72                  return false;
73              }
74              if (getClass() != obj.getClass()) {
75                  return false;
76              }
77              final ArrayKey other = (ArrayKey) obj;
78              return Arrays.deepEquals(keys, other.keys);
79          }
80  
81          @Override
82          public int hashCode() {
83              return hashCode;
84          }
85  
86      }
87  
88      /**
89       * No date or no time.  Used in same parameters as DateFormat.SHORT or DateFormat.LONG
90       */
91      static final int NONE = -1;
92  
93      private static final ConcurrentMap<ArrayKey, String> cDateTimeInstanceCache = new ConcurrentHashMap<>(7);
94  
95      /**
96       * Gets a date/time format for the specified styles and locale.
97       *
98       * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
99       * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
100      * @param locale  The non-null locale of the desired format
101      * @return a localized standard date/time format
102      * @throws IllegalArgumentException if the Locale has no date/time pattern defined
103      */
104     // package protected, for access from test code; do not make public or protected
105     static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
106         final Locale safeLocale = LocaleUtils.toLocale(locale);
107         final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale);
108         return cDateTimeInstanceCache.computeIfAbsent(key, k -> {
109             try {
110                 final DateFormat formatter;
111                 if (dateStyle == null) {
112                     formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale);
113                 } else if (timeStyle == null) {
114                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale);
115                 } else {
116                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale);
117                 }
118                 return ((SimpleDateFormat) formatter).toPattern();
119             } catch (final ClassCastException ex) {
120                 throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale);
121             }
122         });
123     }
124 
125     private final ConcurrentMap<ArrayKey, F> cInstanceCache = new ConcurrentHashMap<>(7);
126 
127     /**
128      * Create a format instance using the specified pattern, time zone
129      * and locale.
130      *
131      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
132      * @param timeZone  time zone, this will not be null.
133      * @param locale  locale, this will not be null.
134      * @return a pattern based date/time formatter
135      * @throws IllegalArgumentException if pattern is invalid
136      *  or {@code null}
137      */
138     protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale);
139 
140     /**
141      * Gets a date formatter instance using the specified style,
142      * time zone and locale.
143      *
144      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
145      * @param timeZone  optional time zone, overrides time zone of
146      *  formatted date, null means use default Locale
147      * @param locale  optional locale, overrides system locale
148      * @return a localized standard date/time formatter
149      * @throws IllegalArgumentException if the Locale has no date/time
150      *  pattern defined
151      */
152     // package protected, for access from FastDateFormat; do not make public or protected
153     F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) {
154         return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
155     }
156 
157     /**
158      * Gets a date/time formatter instance using the specified style,
159      * time zone and locale.
160      *
161      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
162      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
163      * @param timeZone  optional time zone, overrides time zone of
164      *  formatted date, null means use default Locale
165      * @param locale  optional locale, overrides system locale
166      * @return a localized standard date/time formatter
167      * @throws IllegalArgumentException if the Locale has no date/time
168      *  pattern defined
169      */
170     // package protected, for access from FastDateFormat; do not make public or protected
171     F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
172         return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale);
173     }
174 
175     /**
176      * Gets a date/time formatter instance using the specified style,
177      * time zone and locale.
178      *
179      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
180      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
181      * @param timeZone  optional time zone, overrides time zone of
182      *  formatted date, null means use default Locale
183      * @param locale  optional locale, overrides system locale
184      * @return a localized standard date/time formatter
185      * @throws IllegalArgumentException if the Locale has no date/time
186      *  pattern defined
187      */
188     // This must remain private, see LANG-884
189     private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
190         locale = LocaleUtils.toLocale(locale);
191         final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
192         return getInstance(pattern, timeZone, locale);
193     }
194 
195     /**
196      * Gets a formatter instance using the default pattern in the
197      * default time zone and locale.
198      *
199      * @return a date/time formatter
200      */
201     public F getInstance() {
202         return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
203     }
204 
205     /**
206      * Gets a formatter instance using the specified pattern, time zone
207      * and locale.
208      *
209      * @param pattern  {@link java.text.SimpleDateFormat} compatible
210      *  pattern, non-null
211      * @param timeZone  the time zone, null means use the default TimeZone
212      * @param locale  the locale, null means use the default Locale
213      * @return a pattern based date/time formatter
214      * @throws NullPointerException if pattern is {@code null}
215      * @throws IllegalArgumentException if pattern is invalid
216      */
217     public F getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
218         Objects.requireNonNull(pattern, "pattern");
219         final TimeZone actualTimeZone = TimeZones.toTimeZone(timeZone);
220         final Locale actualLocale = LocaleUtils.toLocale(locale);
221         final ArrayKey key = new ArrayKey(pattern, actualTimeZone, actualLocale);
222         return cInstanceCache.computeIfAbsent(key, k -> createInstance(pattern, actualTimeZone, actualLocale));
223     }
224 
225     /**
226      * Gets a time formatter instance using the specified style,
227      * time zone and locale.
228      *
229      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
230      * @param timeZone  optional time zone, overrides time zone of
231      *  formatted date, null means use default Locale
232      * @param locale  optional locale, overrides system locale
233      * @return a localized standard date/time formatter
234      * @throws IllegalArgumentException if the Locale has no date/time
235      *  pattern defined
236      */
237     // package protected, for access from FastDateFormat; do not make public or protected
238     F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
239         return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
240     }
241 
242 }