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