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  
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 }