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
18 package org.apache.logging.log4j.core.util.datetime;
19
20 import java.text.DateFormat;
21 import java.text.SimpleDateFormat;
22 import java.util.Arrays;
23 import java.util.Locale;
24 import java.util.TimeZone;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ConcurrentMap;
27
28 /**
29 * <p>FormatCache is a cache and factory for {@link Format}s.</p>
30 *
31 * <p>
32 * Copied and modified from <a href="https://commons.apache.org/proper/commons-lang/">Apache Commons Lang</a>.
33 * </p>
34 *
35 * @since Apache Commons Lang 3.0
36 */
37 // TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach.
38 abstract class FormatCache<F extends Format> {
39
40 /**
41 * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG
42 */
43 static final int NONE= -1;
44
45 private final ConcurrentMap<MultipartKey, F> cInstanceCache
46 = new ConcurrentHashMap<>(7);
47
48 private static final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
49 = new ConcurrentHashMap<>(7);
50
51 /**
52 * <p>Gets a formatter instance using the default pattern in the
53 * default timezone and locale.</p>
54 *
55 * @return a date/time formatter
56 */
57 public F getInstance() {
58 return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
59 }
60
61 /**
62 * <p>Gets a formatter instance using the specified pattern, time zone
63 * and locale.</p>
64 *
65 * @param pattern {@link java.text.SimpleDateFormat} compatible
66 * pattern, non-null
67 * @param timeZone the time zone, null means use the default TimeZone
68 * @param locale the locale, null means use the default Locale
69 * @return a pattern based date/time formatter
70 * @throws IllegalArgumentException if pattern is invalid
71 * or <code>null</code>
72 */
73 public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {
74 if (pattern == null) {
75 throw new NullPointerException("pattern must not be null");
76 }
77 if (timeZone == null) {
78 timeZone = TimeZone.getDefault();
79 }
80 if (locale == null) {
81 locale = Locale.getDefault();
82 }
83 final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
84 F format = cInstanceCache.get(key);
85 if (format == null) {
86 format = createInstance(pattern, timeZone, locale);
87 final F previousValue= cInstanceCache.putIfAbsent(key, format);
88 if (previousValue != null) {
89 // another thread snuck in and did the same work
90 // we should return the instance that is in ConcurrentMap
91 format= previousValue;
92 }
93 }
94 return format;
95 }
96
97 /**
98 * <p>Create a format instance using the specified pattern, time zone
99 * and locale.</p>
100 *
101 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
102 * @param timeZone time zone, this will not be null.
103 * @param locale locale, this will not be null.
104 * @return a pattern based date/time formatter
105 * @throws IllegalArgumentException if pattern is invalid
106 * or <code>null</code>
107 */
108 abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale);
109
110 /**
111 * <p>Gets a date/time formatter instance using the specified style,
112 * time zone and locale.</p>
113 *
114 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
115 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
116 * @param timeZone optional time zone, overrides time zone of
117 * formatted date, null means use default Locale
118 * @param locale optional locale, overrides system locale
119 * @return a localized standard date/time formatter
120 * @throws IllegalArgumentException if the Locale has no date/time
121 * pattern defined
122 */
123 // This must remain private, see LANG-884
124 private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
125 if (locale == null) {
126 locale = Locale.getDefault();
127 }
128 final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
129 return getInstance(pattern, timeZone, locale);
130 }
131
132 /**
133 * <p>Gets a date/time formatter instance using the specified style,
134 * time zone and locale.</p>
135 *
136 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
137 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
138 * @param timeZone optional time zone, overrides time zone of
139 * formatted date, null means use default Locale
140 * @param locale optional locale, overrides system locale
141 * @return a localized standard date/time formatter
142 * @throws IllegalArgumentException if the Locale has no date/time
143 * pattern defined
144 */
145 // package protected, for access from FastDateFormat; do not make public or protected
146 F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
147 return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale);
148 }
149
150 /**
151 * <p>Gets a date formatter instance using the specified style,
152 * time zone and locale.</p>
153 *
154 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
155 * @param timeZone optional time zone, overrides time zone of
156 * formatted date, null means use default Locale
157 * @param locale optional locale, overrides system locale
158 * @return a localized standard date/time formatter
159 * @throws IllegalArgumentException if the Locale has no date/time
160 * pattern defined
161 */
162 // package protected, for access from FastDateFormat; do not make public or protected
163 F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) {
164 return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
165 }
166
167 /**
168 * <p>Gets a time formatter instance using the specified style,
169 * time zone and locale.</p>
170 *
171 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
172 * @param timeZone optional time zone, overrides time zone of
173 * formatted date, null means use default Locale
174 * @param locale optional locale, overrides system locale
175 * @return a localized standard date/time formatter
176 * @throws IllegalArgumentException if the Locale has no date/time
177 * pattern defined
178 */
179 // package protected, for access from FastDateFormat; do not make public or protected
180 F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
181 return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
182 }
183
184 /**
185 * <p>Gets a date/time format for the specified styles and locale.</p>
186 *
187 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
188 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
189 * @param locale The non-null locale of the desired format
190 * @return a localized standard date/time format
191 * @throws IllegalArgumentException if the Locale has no date/time pattern defined
192 */
193 // package protected, for access from test code; do not make public or protected
194 static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
195 final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
196
197 String pattern = cDateTimeInstanceCache.get(key);
198 if (pattern == null) {
199 try {
200 DateFormat formatter;
201 if (dateStyle == null) {
202 formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale);
203 }
204 else if (timeStyle == null) {
205 formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale);
206 }
207 else {
208 formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale);
209 }
210 pattern = ((SimpleDateFormat)formatter).toPattern();
211 final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
212 if (previous != null) {
213 // even though it doesn't matter if another thread put the pattern
214 // it's still good practice to return the String instance that is
215 // actually in the ConcurrentMap
216 pattern= previous;
217 }
218 } catch (final ClassCastException ex) {
219 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
220 }
221 }
222 return pattern;
223 }
224
225 // ----------------------------------------------------------------------
226 /**
227 * <p>Helper class to hold multi-part Map keys</p>
228 */
229 private static class MultipartKey {
230 private final Object[] keys;
231 private int hashCode;
232
233 /**
234 * Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
235 * @param keys the set of objects that make up the key. Each key may be null.
236 */
237 public MultipartKey(final Object... keys) {
238 this.keys = keys;
239 }
240
241 /**
242 * {@inheritDoc}
243 */
244 @Override
245 public boolean equals(final Object obj) {
246 // Eliminate the usual boilerplate because
247 // this inner static class is only used in a generic ConcurrentHashMap
248 // which will not compare against other Object types
249 return Arrays.equals(keys, ((MultipartKey)obj).keys);
250 }
251
252 /**
253 * {@inheritDoc}
254 */
255 @Override
256 public int hashCode() {
257 if(hashCode==0) {
258 int rc= 0;
259 for(final Object key : keys) {
260 if(key!=null) {
261 rc= rc*7 + key.hashCode();
262 }
263 }
264 hashCode= rc;
265 }
266 return hashCode;
267 }
268 }
269
270 }