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