Coverage Report - org.apache.commons.lang3.time.FormatCache
 
Classes in this File Line Coverage Branch Coverage Complexity
FormatCache
87%
34/39
85%
17/20
3
FormatCache$MultipartKey
100%
11/11
100%
6/6
3
 
 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
 63  
      * @param timeZone  the non-null time zone
 64  
      * @param locale  the non-null 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  1392
         if (pattern == null) {
 71  0
             throw new NullPointerException("pattern must not be null");
 72  
         }
 73  1392
         if (timeZone == null) {
 74  47
             timeZone = TimeZone.getDefault();
 75  
         }
 76  1392
         if (locale == null) {
 77  44
             locale = Locale.getDefault();
 78  
         }
 79  1392
         final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
 80  1392
         F format = cInstanceCache.get(key);
 81  1392
         if (format == null) {           
 82  706
             format = createInstance(pattern, timeZone, locale);
 83  703
             final F previousValue= cInstanceCache.putIfAbsent(key, format);
 84  703
             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  1389
         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
 111  
      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
 112  
      * @param timeZone  optional time zone, overrides time zone of
 113  
      *  formatted date
 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  
     public 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 format for the specified styles and locale.</p>
 129  
      * 
 130  
      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
 131  
      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
 132  
      * @param locale  The non-null locale of the desired format
 133  
      * @return a localized standard date/time format
 134  
      * @throws IllegalArgumentException if the Locale has no date/time pattern defined
 135  
      */
 136  
     public static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
 137  40
         final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
 138  
 
 139  40
         String pattern = cDateTimeInstanceCache.get(key);
 140  40
         if (pattern == null) {
 141  
             try {
 142  
                 DateFormat formatter;
 143  16
                 if (dateStyle == null) {
 144  2
                     formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale);                    
 145  
                 }
 146  14
                 else if (timeStyle == null) {
 147  6
                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale);                    
 148  
                 }
 149  
                 else {
 150  8
                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale);
 151  
                 }
 152  16
                 pattern = ((SimpleDateFormat)formatter).toPattern();
 153  16
                 final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
 154  16
                 if (previous != null) {
 155  
                     // even though it doesn't matter if another thread put the pattern
 156  
                     // it's still good practice to return the String instance that is
 157  
                     // actually in the ConcurrentMap
 158  0
                     pattern= previous;
 159  
                 }
 160  0
             } catch (final ClassCastException ex) {
 161  0
                 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
 162  16
             }
 163  
         }
 164  40
         return pattern;
 165  
     }
 166  
 
 167  
     // ----------------------------------------------------------------------
 168  
     /**
 169  
      * <p>Helper class to hold multi-part Map keys</p>
 170  
      */
 171  1
     private static class MultipartKey {
 172  
         private final Object[] keys;
 173  
         private int hashCode;
 174  
 
 175  
         /**
 176  
          * Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
 177  
          * @param keys the set of objects that make up the key.  Each key may be null.
 178  
          */
 179  1432
         public MultipartKey(final Object... keys) {
 180  1432
             this.keys = keys;
 181  1432
         }
 182  
 
 183  
         /**
 184  
          * {@inheritDoc}
 185  
          */
 186  
         @Override
 187  
         public boolean equals(final Object obj) {
 188  
             // Eliminate the usual boilerplate because
 189  
             // this inner static class is only used in a generic ConcurrentHashMap
 190  
             // which will not compare against other Object types
 191  718
             return Arrays.equals(keys, ((MultipartKey)obj).keys);
 192  
         }
 193  
 
 194  
         /**
 195  
          * {@inheritDoc}
 196  
          */
 197  
         @Override
 198  
         public int hashCode() {
 199  2151
             if(hashCode==0) {
 200  1432
                 int rc= 0;
 201  5728
                 for(final Object key : keys) {
 202  4296
                     if(key!=null) {
 203  4273
                         rc= rc*7 + key.hashCode();
 204  
                     }
 205  
                 }
 206  1432
                 hashCode= rc;
 207  
             }
 208  2151
             return hashCode;
 209  
         }
 210  
     }
 211  
 
 212  
 }