Coverage Report - org.apache.commons.lang3.time.FormatCache
 
Classes in this File Line Coverage Branch Coverage Complexity
FormatCache
88%
37/42
85%
17/20
2,455
FormatCache$MultipartKey
100%
11/11
100%
6/6
2,455
 
 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  1
 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  1
     private final ConcurrentMap<MultipartKey, F> cInstanceCache 
 42  
         = new ConcurrentHashMap<MultipartKey, F>(7);
 43  
     
 44  1
     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  3
         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  1393
         if (pattern == null) {
 71  0
             throw new NullPointerException("pattern must not be null");
 72  
         }
 73  1393
         if (timeZone == null) {
 74  48
             timeZone = TimeZone.getDefault();
 75  
         }
 76  1393
         if (locale == null) {
 77  45
             locale = Locale.getDefault();
 78  
         }
 79  1393
         final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
 80  1393
         F format = cInstanceCache.get(key);
 81  1393
         if (format == null) {           
 82  712
             format = createInstance(pattern, timeZone, locale);
 83  709
             final F previousValue= cInstanceCache.putIfAbsent(key, format);
 84  709
             if (previousValue != null) {
 85  
                 // another thread snuck in and did the same work
 86  
                 // we should return the instance that is in ConcurrentMap
 87  0
                 format= previousValue;              
 88  
             }
 89  
         }
 90  1390
         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  32
         if (locale == null) {
 122  11
             locale = Locale.getDefault();
 123  
         }
 124  32
         final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
 125  32
         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  17
         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  9
         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  6
         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  40
         final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
 192  
 
 193  40
         String pattern = cDateTimeInstanceCache.get(key);
 194  40
         if (pattern == null) {
 195  
             try {
 196  
                 DateFormat formatter;
 197  17
                 if (dateStyle == null) {
 198  2
                     formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale);                    
 199  
                 }
 200  15
                 else if (timeStyle == null) {
 201  6
                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale);                    
 202  
                 }
 203  
                 else {
 204  9
                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale);
 205  
                 }
 206  17
                 pattern = ((SimpleDateFormat)formatter).toPattern();
 207  17
                 final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
 208  17
                 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  0
                     pattern= previous;
 213  
                 }
 214  0
             } catch (final ClassCastException ex) {
 215  0
                 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
 216  17
             }
 217  
         }
 218  40
         return pattern;
 219  
     }
 220  
 
 221  
     // ----------------------------------------------------------------------
 222  
     /**
 223  
      * <p>Helper class to hold multi-part Map keys</p>
 224  
      */
 225  1
     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  1433
         public MultipartKey(final Object... keys) {
 234  1433
             this.keys = keys;
 235  1433
         }
 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  712
             return Arrays.equals(keys, ((MultipartKey)obj).keys);
 246  
         }
 247  
 
 248  
         /**
 249  
          * {@inheritDoc}
 250  
          */
 251  
         @Override
 252  
         public int hashCode() {
 253  2159
             if(hashCode==0) {
 254  1433
                 int rc= 0;
 255  5732
                 for(final Object key : keys) {
 256  4299
                     if(key!=null) {
 257  4276
                         rc= rc*7 + key.hashCode();
 258  
                     }
 259  
                 }
 260  1433
                 hashCode= rc;
 261  
             }
 262  2159
             return hashCode;
 263  
         }
 264  
     }
 265  
 
 266  
 }