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      /**
90       * No date or no time.  Used in same parameters as DateFormat.SHORT or DateFormat.LONG
91       */
92      static final int NONE = -1;
93  
94      private static final ConcurrentMap<ArrayKey, String> cDateTimeInstanceCache = new ConcurrentHashMap<>(7);
95  
96      /**
97       * Gets a date/time format for the specified styles and locale.
98       *
99       * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
100      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
101      * @param locale  The non-null locale of the desired format
102      * @return a localized standard date/time format
103      * @throws IllegalArgumentException if the Locale has no date/time pattern defined
104      */
105     // package protected, for access from test code; do not make public or protected
106     static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
107         final Locale safeLocale = LocaleUtils.toLocale(locale);
108         final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale);
109         return cDateTimeInstanceCache.computeIfAbsent(key, k -> {
110             try {
111                 final DateFormat formatter;
112                 if (dateStyle == null) {
113                     formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale);
114                 } else if (timeStyle == null) {
115                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale);
116                 } else {
117                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale);
118                 }
119                 return ((SimpleDateFormat) formatter).toPattern();
120             } catch (final ClassCastException ex) {
121                 throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale);
122             }
123         });
124     }
125 
126     private final ConcurrentMap<ArrayKey, F> cInstanceCache = new ConcurrentHashMap<>(7);
127 
128     /**
129      * Create a format instance using the specified pattern, time zone
130      * and locale.
131      *
132      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
133      * @param timeZone  time zone, this will not be null.
134      * @param locale  locale, this will not be null.
135      * @return a pattern based date/time formatter
136      * @throws IllegalArgumentException if pattern is invalid
137      *  or {@code null}
138      */
139     protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale);
140 
141     /**
142      * Gets a date formatter instance using the specified style,
143      * time zone and locale.
144      *
145      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
146      * @param timeZone  optional time zone, overrides time zone of
147      *  formatted date, null means use default Locale
148      * @param locale  optional locale, overrides system locale
149      * @return a localized standard date/time formatter
150      * @throws IllegalArgumentException if the Locale has no date/time
151      *  pattern defined
152      */
153     // package protected, for access from FastDateFormat; do not make public or protected
154     F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) {
155         return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
156     }
157 
158     /**
159      * Gets a date/time formatter instance using the specified style,
160      * time zone and locale.
161      *
162      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
163      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
164      * @param timeZone  optional time zone, overrides time zone of
165      *  formatted date, null means use default Locale
166      * @param locale  optional locale, overrides system locale
167      * @return a localized standard date/time formatter
168      * @throws IllegalArgumentException if the Locale has no date/time
169      *  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
183      *  formatted date, null means use default Locale
184      * @param locale  optional locale, overrides system locale
185      * @return a localized standard date/time formatter
186      * @throws IllegalArgumentException if the Locale has no date/time
187      *  pattern defined
188      */
189     // This must remain private, see LANG-884
190     private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
191         locale = LocaleUtils.toLocale(locale);
192         final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
193         return getInstance(pattern, timeZone, locale);
194     }
195 
196     /**
197      * Gets a formatter instance using the default pattern in the
198      * default time zone and locale.
199      *
200      * @return a date/time formatter
201      */
202     public F getInstance() {
203         return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
204     }
205 
206     /**
207      * Gets a formatter instance using the specified pattern, time zone
208      * and locale.
209      *
210      * @param pattern  {@link java.text.SimpleDateFormat} compatible
211      *  pattern, non-null
212      * @param timeZone  the time zone, null means use the default TimeZone
213      * @param locale  the locale, null means use the default Locale
214      * @return a pattern based date/time formatter
215      * @throws NullPointerException if pattern is {@code null}
216      * @throws IllegalArgumentException if pattern is invalid
217      */
218     public F getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
219         Objects.requireNonNull(pattern, "pattern");
220         final TimeZone actualTimeZone = TimeZones.toTimeZone(timeZone);
221         final Locale actualLocale = LocaleUtils.toLocale(locale);
222         final ArrayKey key = new ArrayKey(pattern, actualTimeZone, actualLocale);
223         return cInstanceCache.computeIfAbsent(key, k -> createInstance(pattern, actualTimeZone, actualLocale));
224     }
225 
226     /**
227      * Gets a time formatter instance using the specified style,
228      * time zone and locale.
229      *
230      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
231      * @param timeZone  optional time zone, overrides time zone of
232      *  formatted date, null means use default Locale
233      * @param locale  optional locale, overrides system locale
234      * @return a localized standard date/time formatter
235      * @throws IllegalArgumentException if the Locale has no date/time
236      *  pattern defined
237      */
238     // package protected, for access from FastDateFormat; do not make public or protected
239     F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
240         return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
241     }
242 
243 }