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