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.Objects; 25 import java.util.TimeZone; 26 import java.util.concurrent.ConcurrentHashMap; 27 import java.util.concurrent.ConcurrentMap; 28 29 import org.apache.commons.lang3.LocaleUtils; 30 31 /** 32 * FormatCache is a cache and factory for {@link Format}s. 33 * 34 * @param <F> The Format type. 35 * 36 * @since 3.0 37 */ 38 // TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach. 39 abstract class FormatCache<F extends Format> { 40 41 /** 42 * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG 43 */ 44 static final int NONE = -1; 45 46 private final ConcurrentMap<ArrayKey, F> cInstanceCache = new ConcurrentHashMap<>(7); 47 48 private static final ConcurrentMap<ArrayKey, String> cDateTimeInstanceCache = new ConcurrentHashMap<>(7); 49 50 /** 51 * Gets a formatter instance using the default pattern in the 52 * default time zone and locale. 53 * 54 * @return a date/time formatter 55 */ getInstance()56 public F getInstance() { 57 return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); 58 } 59 60 /** 61 * Gets a formatter instance using the specified pattern, time zone 62 * and locale. 63 * 64 * @param pattern {@link java.text.SimpleDateFormat} compatible 65 * pattern, non-null 66 * @param timeZone the time zone, null means use the default TimeZone 67 * @param locale the locale, null means use the default Locale 68 * @return a pattern based date/time formatter 69 * @throws NullPointerException if pattern is {@code null} 70 * @throws IllegalArgumentException if pattern is invalid 71 */ getInstance(final String pattern, final TimeZone timeZone, final Locale locale)72 public F getInstance(final String pattern, final TimeZone timeZone, final Locale locale) { 73 Objects.requireNonNull(pattern, "pattern"); 74 final TimeZone actualTimeZone = TimeZones.toTimeZone(timeZone); 75 final Locale actualLocale = LocaleUtils.toLocale(locale); 76 final ArrayKey key = new ArrayKey(pattern, actualTimeZone, actualLocale); 77 return cInstanceCache.computeIfAbsent(key, k -> createInstance(pattern, actualTimeZone, actualLocale)); 78 } 79 80 /** 81 * Create a format instance using the specified pattern, time zone 82 * and locale. 83 * 84 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern, this will not be null. 85 * @param timeZone time zone, this will not be null. 86 * @param locale locale, this will not be null. 87 * @return a pattern based date/time formatter 88 * @throws IllegalArgumentException if pattern is invalid 89 * or {@code null} 90 */ createInstance(String pattern, TimeZone timeZone, Locale locale)91 protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale); 92 93 /** 94 * Gets a date/time formatter instance using the specified style, 95 * time zone and locale. 96 * 97 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format 98 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format 99 * @param timeZone optional time zone, overrides time zone of 100 * formatted date, null means use default Locale 101 * @param locale optional locale, overrides system locale 102 * @return a localized standard date/time formatter 103 * @throws IllegalArgumentException if the Locale has no date/time 104 * pattern defined 105 */ 106 // This must remain private, see LANG-884 getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale)107 private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { 108 locale = LocaleUtils.toLocale(locale); 109 final String pattern = getPatternForStyle(dateStyle, timeStyle, locale); 110 return getInstance(pattern, timeZone, locale); 111 } 112 113 /** 114 * Gets a date/time formatter instance using the specified style, 115 * time zone and locale. 116 * 117 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 118 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 119 * @param timeZone optional time zone, overrides time zone of 120 * formatted date, null means use default Locale 121 * @param locale optional locale, overrides system locale 122 * @return a localized standard date/time formatter 123 * @throws IllegalArgumentException if the Locale has no date/time 124 * pattern defined 125 */ 126 // package protected, for access from FastDateFormat; do not make public or protected getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale)127 F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) { 128 return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale); 129 } 130 131 /** 132 * Gets a date formatter instance using the specified style, 133 * time zone and locale. 134 * 135 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 136 * @param timeZone optional time zone, overrides time zone of 137 * formatted date, null means use default Locale 138 * @param locale optional locale, overrides system locale 139 * @return a localized standard date/time formatter 140 * @throws IllegalArgumentException if the Locale has no date/time 141 * pattern defined 142 */ 143 // package protected, for access from FastDateFormat; do not make public or protected getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale)144 F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) { 145 return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale); 146 } 147 148 /** 149 * Gets a time formatter instance using the specified style, 150 * time zone and locale. 151 * 152 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 153 * @param timeZone optional time zone, overrides time zone of 154 * formatted date, null means use default Locale 155 * @param locale optional locale, overrides system locale 156 * @return a localized standard date/time formatter 157 * @throws IllegalArgumentException if the Locale has no date/time 158 * pattern defined 159 */ 160 // package protected, for access from FastDateFormat; do not make public or protected getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale)161 F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) { 162 return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale); 163 } 164 165 /** 166 * Gets a date/time format for the specified styles and locale. 167 * 168 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format 169 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format 170 * @param locale The non-null locale of the desired format 171 * @return a localized standard date/time format 172 * @throws IllegalArgumentException if the Locale has no date/time pattern defined 173 */ 174 // package protected, for access from test code; do not make public or protected getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale)175 static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) { 176 final Locale safeLocale = LocaleUtils.toLocale(locale); 177 final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale); 178 return cDateTimeInstanceCache.computeIfAbsent(key, k -> { 179 try { 180 final DateFormat formatter; 181 if (dateStyle == null) { 182 formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale); 183 } else if (timeStyle == null) { 184 formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale); 185 } else { 186 formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale); 187 } 188 return ((SimpleDateFormat) formatter).toPattern(); 189 } catch (final ClassCastException ex) { 190 throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale); 191 } 192 }); 193 } 194 195 /** 196 * Helper class to hold multipart Map keys as arrays. 197 */ 198 private static final class ArrayKey { 199 200 private static int computeHashCode(final Object[] keys) { 201 final int prime = 31; 202 int result = 1; 203 result = prime * result + Arrays.hashCode(keys); 204 return result; 205 } 206 207 private final Object[] keys; 208 private final int hashCode; 209 210 /** 211 * Constructs an instance of {@link MultipartKey} to hold the specified objects. 212 * 213 * @param keys the set of objects that make up the key. Each key may be null. 214 */ 215 ArrayKey(final Object... keys) { 216 this.keys = keys; 217 this.hashCode = computeHashCode(keys); 218 } 219 220 @Override 221 public int hashCode() { 222 return hashCode; 223 } 224 225 @Override 226 public boolean equals(final Object obj) { 227 if (this == obj) { 228 return true; 229 } 230 if (obj == null) { 231 return false; 232 } 233 if (getClass() != obj.getClass()) { 234 return false; 235 } 236 final ArrayKey other = (ArrayKey) obj; 237 return Arrays.deepEquals(keys, other.keys); 238 } 239 240 241 } 242 243 } 244