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