| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| FormatCache |
|
| 3.0;3 | ||||
| FormatCache$MultipartKey |
|
| 3.0;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 | } |