Coverage Report - org.apache.commons.lang3.time.FormatCache
 
Classes in this File Line Coverage Branch Coverage Complexity
FormatCache
90%
36/40
88%
16/18
2,273
FormatCache$MultipartKey
100%
11/11
100%
6/6
2,273
 
 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  
 import org.apache.commons.lang3.Validate;
 29  
 
 30  
 /**
 31  
  * <p>FormatCache is a cache and factory for {@link Format}s.</p>
 32  
  *
 33  
  * @since 3.0
 34  
  */
 35  
 // TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach.
 36  1
 abstract class FormatCache<F extends Format> {
 37  
 
 38  
     /**
 39  
      * No date or no time.  Used in same parameters as DateFormat.SHORT or DateFormat.LONG
 40  
      */
 41  
     static final int NONE= -1;
 42  
 
 43  1
     private final ConcurrentMap<MultipartKey, F> cInstanceCache
 44  
         = new ConcurrentHashMap<>(7);
 45  
 
 46  1
     private static final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
 47  
         = new ConcurrentHashMap<>(7);
 48  
 
 49  
     /**
 50  
      * <p>Gets a formatter instance using the default pattern in the
 51  
      * default timezone and locale.</p>
 52  
      *
 53  
      * @return a date/time formatter
 54  
      */
 55  
     public F getInstance() {
 56  3
         return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
 57  
     }
 58  
 
 59  
     /**
 60  
      * <p>Gets a formatter instance using the specified pattern, time zone
 61  
      * and locale.</p>
 62  
      *
 63  
      * @param pattern  {@link java.text.SimpleDateFormat} compatible
 64  
      *  pattern, non-null
 65  
      * @param timeZone  the time zone, null means use the default TimeZone
 66  
      * @param locale  the locale, null means use the default Locale
 67  
      * @return a pattern based date/time formatter
 68  
      * @throws IllegalArgumentException if pattern is invalid
 69  
      *  or <code>null</code>
 70  
      */
 71  
     public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {
 72  2128
         Validate.notNull(pattern, "pattern must not be null");
 73  2128
         if (timeZone == null) {
 74  52
             timeZone = TimeZone.getDefault();
 75  
         }
 76  2128
         if (locale == null) {
 77  711
             locale = Locale.getDefault();
 78  
         }
 79  2128
         final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
 80  2128
         F format = cInstanceCache.get(key);
 81  2128
         if (format == null) {
 82  1399
             format = createInstance(pattern, timeZone, locale);
 83  1394
             final F previousValue= cInstanceCache.putIfAbsent(key, format);
 84  1394
             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  2123
         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  
     protected abstract 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, final 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, final 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, final 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  15
                 } else if (timeStyle == null) {
 200  6
                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale);
 201  
                 } else {
 202  9
                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale);
 203  
                 }
 204  17
                 pattern = ((SimpleDateFormat)formatter).toPattern();
 205  17
                 final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
 206  17
                 if (previous != null) {
 207  
                     // even though it doesn't matter if another thread put the pattern
 208  
                     // it's still good practice to return the String instance that is
 209  
                     // actually in the ConcurrentMap
 210  0
                     pattern= previous;
 211  
                 }
 212  0
             } catch (final ClassCastException ex) {
 213  0
                 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
 214  17
             }
 215  
         }
 216  40
         return pattern;
 217  
     }
 218  
 
 219  
     // ----------------------------------------------------------------------
 220  
     /**
 221  
      * <p>Helper class to hold multi-part Map keys</p>
 222  
      */
 223  
     private static class MultipartKey {
 224  
         private final Object[] keys;
 225  
         private int hashCode;
 226  
 
 227  
         /**
 228  
          * Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
 229  
          * @param keys the set of objects that make up the key.  Each key may be null.
 230  
          */
 231  2168
         MultipartKey(final Object... keys) {
 232  2168
             this.keys = keys;
 233  2168
         }
 234  
 
 235  
         /**
 236  
          * {@inheritDoc}
 237  
          */
 238  
         @Override
 239  
         public boolean equals(final Object obj) {
 240  
             // Eliminate the usual boilerplate because
 241  
             // this inner static class is only used in a generic ConcurrentHashMap
 242  
             // which will not compare against other Object types
 243  2262
             return Arrays.equals(keys, ((MultipartKey)obj).keys);
 244  
         }
 245  
 
 246  
         /**
 247  
          * {@inheritDoc}
 248  
          */
 249  
         @Override
 250  
         public int hashCode() {
 251  3579
             if(hashCode==0) {
 252  2168
                 int rc= 0;
 253  8672
                 for(final Object key : keys) {
 254  6504
                     if(key!=null) {
 255  6481
                         rc= rc*7 + key.hashCode();
 256  
                     }
 257  
                 }
 258  2168
                 hashCode= rc;
 259  
             }
 260  3579
             return hashCode;
 261  
         }
 262  
     }
 263  
 
 264  
 }