/* * 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 org.apache.commons.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; /** * <p>FormatCache is a cache and factory for {@link Format}s.</p> * * @since 3.0 * @version $Id: FormatCache 892161 2009-12-18 07:21:10Z $ */ // TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach. abstract class FormatCache<F extends Format> { /** * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG */ static final int NONE= -1; private final ConcurrentMap<MultipartKey, F> cInstanceCache = new ConcurrentHashMap<MultipartKey, F>(7); private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache = new ConcurrentHashMap<MultipartKey, String>(7); /** * <p>Gets a formatter instance using the default pattern in the * default timezone and locale.</p> * * @return a date/time formatter */ public F getInstance() { return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); } /** * <p>Gets a formatter instance using the specified pattern, time zone * and locale.</p> * * @param pattern {@link java.text.SimpleDateFormat} compatible * pattern * @param timeZone the non-null time zone * @param locale the non-null locale * @return a pattern based date/time formatter * @throws IllegalArgumentException if pattern is invalid * or <code>null</code> */ public F getInstance(String pattern, TimeZone timeZone, Locale locale) { if (pattern == null) { throw new NullPointerException("pattern must not be null"); } if (timeZone == null) { timeZone = TimeZone.getDefault(); } if (locale == null) { locale = Locale.getDefault(); } MultipartKey key = new MultipartKey(pattern, timeZone, locale); F format = cInstanceCache.get(key); if (format == null) { format = createInstance(pattern, timeZone, locale); 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; } /** * <p>Create a format instance using the specified pattern, time zone * and locale.</p> * * @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</code> */ abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); /** * <p>Gets a date/time formatter instance using the specified style, * time zone and locale.</p> * * @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 * @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 */ public F getDateTimeInstance(Integer dateStyle, Integer timeStyle, TimeZone timeZone, Locale locale) { if (locale == null) { locale = Locale.getDefault(); } 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, locale); } else if (timeStyle == null) { formatter = DateFormat.getDateInstance(dateStyle, locale); } else { formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); } pattern = ((SimpleDateFormat)formatter).toPattern(); 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 (ClassCastException ex) { throw new IllegalArgumentException("No date time pattern for locale: " + locale); } } return getInstance(pattern, timeZone, locale); } // ---------------------------------------------------------------------- /** * <p>Helper class to hold multi-part Map keys</p> */ private static class MultipartKey { private final Object[] keys; private int hashCode; /** * Constructs an instance of <code>MultipartKey</code> to hold the specified objects. * @param keys the set of objects that make up the key. Each key may be null. */ public MultipartKey(Object... keys) { this.keys = keys; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if ( obj instanceof MultipartKey == false ) { return false; } return Arrays.equals(keys, ((MultipartKey)obj).keys); } /** * {@inheritDoc} */ @Override public int hashCode() { if(hashCode==0) { int rc= 0; for(Object key : keys) { if(key!=null) { rc= rc*7 + key.hashCode(); } } hashCode= rc; } return hashCode; } } }