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