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