com.feilong.lib.lang3.time.FormatCache Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.feilong.lib.lang3.time;
import java.text.DateFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.feilong.lib.lang3.Validate;
/**
*
* FormatCache is a cache and factory for {@link Format}s.
*
*
* @since 3.0
*/
// TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach.
abstract class FormatCache {
/**
* No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG
*/
static final int NONE = -1;
private final ConcurrentMap cInstanceCache = new ConcurrentHashMap<>(7);
private static final ConcurrentMap cDateTimeInstanceCache = new ConcurrentHashMap<>(7);
/**
*
* Gets a formatter instance using the default pattern in the
* default timezone and locale.
*
*
* @return a date/time formatter
*/
public F getInstance(){
return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
}
/**
*
* Gets a formatter instance using the specified pattern, time zone
* and locale.
*
*
* @param pattern
* {@link java.text.SimpleDateFormat} compatible
* pattern, non-null
* @param timeZone
* the time zone, null means use the default TimeZone
* @param locale
* the locale, null means use the default Locale
* @return a pattern based date/time formatter
* @throws IllegalArgumentException
* if pattern is invalid
* or {@code null}
*/
public F getInstance(final String pattern,TimeZone timeZone,Locale locale){
Validate.notNull(pattern, "pattern must not be null");
if (timeZone == null){
timeZone = TimeZone.getDefault();
}
if (locale == null){
locale = Locale.getDefault();
}
final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
F format = cInstanceCache.get(key);
if (format == null){
format = createInstance(pattern, timeZone, locale);
final F previousValue = cInstanceCache.putIfAbsent(key, format);
if (previousValue != null){
// another thread snuck in and did the same work
// we should return the instance that is in ConcurrentMap
format = previousValue;
}
}
return format;
}
/**
*
* Create a format instance using the specified pattern, time zone
* and locale.
*
*
* @param pattern
* {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
* @param timeZone
* time zone, this will not be null.
* @param locale
* locale, this will not be null.
* @return a pattern based date/time formatter
* @throws IllegalArgumentException
* if pattern is invalid
* or {@code null}
*/
protected abstract F createInstance(String pattern,TimeZone timeZone,Locale locale);
/**
*
* Gets a date/time formatter instance using the specified style,
* time zone and locale.
*
*
* @param dateStyle
* date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
* @param timeStyle
* time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
* @param timeZone
* optional time zone, overrides time zone of
* formatted date, null means use default Locale
* @param locale
* optional locale, overrides system locale
* @return a localized standard date/time formatter
* @throws IllegalArgumentException
* if the Locale has no date/time
* pattern defined
*/
// This must remain private, see LANG-884
private F getDateTimeInstance(final Integer dateStyle,final Integer timeStyle,final TimeZone timeZone,Locale locale){
if (locale == null){
locale = Locale.getDefault();
}
final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
return getInstance(pattern, timeZone, locale);
}
/**
*
* Gets a date/time formatter instance using the specified style,
* time zone and locale.
*
*
* @param dateStyle
* date style: FULL, LONG, MEDIUM, or SHORT
* @param timeStyle
* time style: FULL, LONG, MEDIUM, or SHORT
* @param timeZone
* optional time zone, overrides time zone of
* formatted date, null means use default Locale
* @param locale
* optional locale, overrides system locale
* @return a localized standard date/time formatter
* @throws IllegalArgumentException
* if the Locale has no date/time
* pattern defined
*/
// package protected, for access from FastDateFormat; do not make public or protected
F getDateTimeInstance(final int dateStyle,final int timeStyle,final TimeZone timeZone,final Locale locale){
return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale);
}
/**
*
* Gets a date formatter instance using the specified style,
* time zone and locale.
*
*
* @param dateStyle
* date style: FULL, LONG, MEDIUM, or SHORT
* @param timeZone
* optional time zone, overrides time zone of
* formatted date, null means use default Locale
* @param locale
* optional locale, overrides system locale
* @return a localized standard date/time formatter
* @throws IllegalArgumentException
* if the Locale has no date/time
* pattern defined
*/
// package protected, for access from FastDateFormat; do not make public or protected
F getDateInstance(final int dateStyle,final TimeZone timeZone,final Locale locale){
return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
}
/**
*
* Gets a time formatter instance using the specified style,
* time zone and locale.
*
*
* @param timeStyle
* time style: FULL, LONG, MEDIUM, or SHORT
* @param timeZone
* optional time zone, overrides time zone of
* formatted date, null means use default Locale
* @param locale
* optional locale, overrides system locale
* @return a localized standard date/time formatter
* @throws IllegalArgumentException
* if the Locale has no date/time
* pattern defined
*/
// package protected, for access from FastDateFormat; do not make public or protected
F getTimeInstance(final int timeStyle,final TimeZone timeZone,final Locale locale){
return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
}
/**
*
* Gets a date/time format for the specified styles and locale.
*
*
* @param dateStyle
* date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
* @param timeStyle
* time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
* @param locale
* The non-null locale of the desired format
* @return a localized standard date/time format
* @throws IllegalArgumentException
* if the Locale has no date/time pattern defined
*/
// package protected, for access from test code; do not make public or protected
static String getPatternForStyle(final Integer dateStyle,final Integer timeStyle,final Locale locale){
final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
String pattern = cDateTimeInstanceCache.get(key);
if (pattern == null){
try{
DateFormat formatter;
if (dateStyle == null){
formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale);
}else if (timeStyle == null){
formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale);
}else{
formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale);
}
pattern = ((SimpleDateFormat) formatter).toPattern();
final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
if (previous != null){
// even though it doesn't matter if another thread put the pattern
// it's still good practice to return the String instance that is
// actually in the ConcurrentMap
pattern = previous;
}
}catch (final ClassCastException ex){
throw new IllegalArgumentException("No date time pattern for locale: " + locale);
}
}
return pattern;
}
// ----------------------------------------------------------------------
/**
*
* Helper class to hold multi-part Map keys
*
*/
private static class MultipartKey{
private final Object[] keys;
private int hashCode;
/**
* Constructs an instance of {@code MultipartKey} to hold the specified objects.
*
* @param keys
* the set of objects that make up the key. Each key may be null.
*/
MultipartKey(final Object...keys){
this.keys = keys;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object obj){
// Eliminate the usual boilerplate because
// this inner static class is only used in a generic ConcurrentHashMap
// which will not compare against other Object types
return Arrays.equals(keys, ((MultipartKey) obj).keys);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode(){
if (hashCode == 0){
int rc = 0;
for (final Object key : keys){
if (key != null){
rc = rc * 7 + key.hashCode();
}
}
hashCode = rc;
}
return hashCode;
}
}
}