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