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    *      https://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   * Caches for {@link Format} instances.
33   *
34   * @param <F> The Format type.
35   * @since 3.0
36   */
37  // TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach.
38  abstract class AbstractFormatCache<F extends Format> {
39  
40      /**
41       * Helper class to hold multipart Map keys as arrays.
42       */
43      private static final class ArrayKey {
44  
45          private final Object[] keys;
46          private final int hashCode;
47  
48          /**
49           * Constructs an instance of {@link MultipartKey} to hold the specified objects.
50           *
51           * @param keys the set of objects that make up the key.  Each key may be null.
52           */
53          ArrayKey(final Object... keys) {
54              this.keys = keys;
55              this.hashCode = Objects.hash(keys);
56          }
57  
58          @Override
59          public boolean equals(final Object obj) {
60              if (this == obj) {
61                  return true;
62              }
63              if (obj == null) {
64                  return false;
65              }
66              if (getClass() != obj.getClass()) {
67                  return false;
68              }
69              final ArrayKey other = (ArrayKey) obj;
70              return Arrays.deepEquals(keys, other.keys);
71          }
72  
73          @Override
74          public int hashCode() {
75              return hashCode;
76          }
77  
78      }
79  
80      /**
81       * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG.
82       */
83      static final int NONE = -1;
84  
85      private static final ConcurrentMap<ArrayKey, String> dateTimeInstanceCache = new ConcurrentHashMap<>(7);
86  
87      /**
88       * Clears the cache.
89       */
90      static void clear() {
91          dateTimeInstanceCache.clear();
92      }
93  
94      /**
95       * Gets a date/time format for the specified styles and locale.
96       *
97       * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format.
98       * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format.
99       * @param locale  The non-null locale of the desired format.
100      * @return a localized standard date/time format.
101      * @throws IllegalArgumentException if the Locale has no date/time pattern defined.
102      */
103     // package protected, for access from test code; do not make public or protected
104     static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
105         final Locale safeLocale = LocaleUtils.toLocale(locale);
106         final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale);
107         return dateTimeInstanceCache.computeIfAbsent(key, k -> {
108             try {
109                 final DateFormat formatter;
110                 if (dateStyle == null) {
111                     formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale);
112                 } else if (timeStyle == null) {
113                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale);
114                 } else {
115                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale);
116                 }
117                 return ((SimpleDateFormat) formatter).toPattern();
118             } catch (final ClassCastException ex) {
119                 throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale);
120             }
121         });
122     }
123 
124     private final ConcurrentMap<ArrayKey, F> instanceCache = new ConcurrentHashMap<>(7);
125 
126     /**
127      * Clears the cache.
128      */
129     void clearInstance() {
130         instanceCache.clear();
131     }
132 
133     /**
134      * Create a format instance using the specified pattern, time zone
135      * and locale.
136      *
137      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
138      * @param timeZone  time zone, this will not be null.
139      * @param locale  locale, this will not be null.
140      * @return a pattern based date/time formatter.
141      * @throws IllegalArgumentException if pattern is invalid or {@code null}.
142      */
143     protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale);
144 
145     /**
146      * Gets a date formatter instance using the specified style,
147      * time zone and locale.
148      *
149      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT.
150      * @param timeZone  optional time zone, overrides time zone of formatted date, null means use default Locale.
151      * @param locale  optional locale, overrides system locale.
152      * @return a localized standard date/time formatter.
153      * @throws IllegalArgumentException if the Locale has no date/time pattern defined.
154      */
155     // package protected, for access from FastDateFormat; do not make public or protected
156     F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) {
157         return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
158     }
159 
160     /**
161      * Gets a date/time formatter instance using the specified style,
162      * time zone and locale.
163      *
164      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT.
165      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT.
166      * @param timeZone  optional time zone, overrides time zone of formatted date, null means use default Locale.
167      * @param locale  optional locale, overrides system locale.
168      * @return a localized standard date/time formatter.
169      * @throws IllegalArgumentException if the Locale has no date/time pattern defined.
170      */
171     // package protected, for access from FastDateFormat; do not make public or protected
172     F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
173         return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale);
174     }
175 
176     /**
177      * Gets a date/time formatter instance using the specified style,
178      * time zone and locale.
179      *
180      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format.
181      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format.
182      * @param timeZone  optional time zone, overrides time zone of 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 pattern defined.
186      */
187     // This must remain private, see LANG-884
188     private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
189         locale = LocaleUtils.toLocale(locale);
190         return getInstance(getPatternForStyle(dateStyle, timeStyle, locale), timeZone, locale);
191     }
192 
193     /**
194      * Gets a formatter instance using the default pattern in the
195      * default time zone and locale.
196      *
197      * @return a date/time formatter.
198      */
199     public F getInstance() {
200         return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
201     }
202 
203     /**
204      * Gets a formatter instance using the specified pattern, time zone
205      * and locale.
206      *
207      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern, non-null.
208      * @param timeZone  the time zone, null means use the default TimeZone.
209      * @param locale  the locale, null means use the default Locale.
210      * @return a pattern based date/time formatter.
211      * @throws NullPointerException if pattern is {@code null}.
212      * @throws IllegalArgumentException if pattern is invalid.
213      */
214     public F getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
215         Objects.requireNonNull(pattern, "pattern");
216         final TimeZone actualTimeZone = TimeZones.toTimeZone(timeZone);
217         final Locale actualLocale = LocaleUtils.toLocale(locale);
218         final ArrayKey key = new ArrayKey(pattern, actualTimeZone, actualLocale);
219         return instanceCache.computeIfAbsent(key, k -> createInstance(pattern, actualTimeZone, actualLocale));
220     }
221 
222     /**
223      * Gets a time formatter instance using the specified style,
224      * time zone and locale.
225      *
226      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT.
227      * @param timeZone  optional time zone, overrides time zone of formatted date, null means use default Locale.
228      * @param locale  optional locale, overrides system locale.
229      * @return a localized standard date/time formatter.
230      * @throws IllegalArgumentException if the Locale has no date/time pattern defined.
231      */
232     // package protected, for access from FastDateFormat; do not make public or protected
233     F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
234         return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
235     }
236 
237 }