AbstractFormatCache.java

  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.  *      https://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. import java.text.DateFormat;
  19. import java.text.Format;
  20. import java.text.SimpleDateFormat;
  21. import java.util.Arrays;
  22. import java.util.Locale;
  23. import java.util.Objects;
  24. import java.util.TimeZone;
  25. import java.util.concurrent.ConcurrentHashMap;
  26. import java.util.concurrent.ConcurrentMap;

  27. import org.apache.commons.lang3.LocaleUtils;

  28. /**
  29.  * FormatCache is a cache and factory for {@link Format}s.
  30.  *
  31.  * @param <F> The Format type.
  32.  * @since 3.0
  33.  */
  34. // TODO: Before making public move from getDateTimeInstance(Integer, ...) to int; or some other approach.
  35. abstract class AbstractFormatCache<F extends Format> {

  36.     /**
  37.      * Helper class to hold multipart Map keys as arrays.
  38.      */
  39.     private static final class ArrayKey {

  40.         private final Object[] keys;
  41.         private final int hashCode;

  42.         /**
  43.          * Constructs an instance of {@link MultipartKey} to hold the specified objects.
  44.          *
  45.          * @param keys the set of objects that make up the key.  Each key may be null.
  46.          */
  47.         ArrayKey(final Object... keys) {
  48.             this.keys = keys;
  49.             this.hashCode = Objects.hash(keys);
  50.         }

  51.         @Override
  52.         public boolean equals(final Object obj) {
  53.             if (this == obj) {
  54.                 return true;
  55.             }
  56.             if (obj == null) {
  57.                 return false;
  58.             }
  59.             if (getClass() != obj.getClass()) {
  60.                 return false;
  61.             }
  62.             final ArrayKey other = (ArrayKey) obj;
  63.             return Arrays.deepEquals(keys, other.keys);
  64.         }

  65.         @Override
  66.         public int hashCode() {
  67.             return hashCode;
  68.         }

  69.     }

  70.     /**
  71.      * No date or no time.  Used in same parameters as DateFormat.SHORT or DateFormat.LONG
  72.      */
  73.     static final int NONE = -1;

  74.     private static final ConcurrentMap<ArrayKey, String> dateTimeInstanceCache = new ConcurrentHashMap<>(7);

  75.     /**
  76.      * Clears the cache.
  77.      */
  78.     static void clear() {
  79.         dateTimeInstanceCache.clear();
  80.     }

  81.     /**
  82.      * Gets a date/time format for the specified styles and locale.
  83.      *
  84.      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
  85.      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
  86.      * @param locale  The non-null locale of the desired format
  87.      * @return a localized standard date/time format
  88.      * @throws IllegalArgumentException if the Locale has no date/time pattern defined
  89.      */
  90.     // package protected, for access from test code; do not make public or protected
  91.     static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
  92.         final Locale safeLocale = LocaleUtils.toLocale(locale);
  93.         final ArrayKey key = new ArrayKey(dateStyle, timeStyle, safeLocale);
  94.         return dateTimeInstanceCache.computeIfAbsent(key, k -> {
  95.             try {
  96.                 final DateFormat formatter;
  97.                 if (dateStyle == null) {
  98.                     formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale);
  99.                 } else if (timeStyle == null) {
  100.                     formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale);
  101.                 } else {
  102.                     formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale);
  103.                 }
  104.                 return ((SimpleDateFormat) formatter).toPattern();
  105.             } catch (final ClassCastException ex) {
  106.                 throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale);
  107.             }
  108.         });
  109.     }

  110.     private final ConcurrentMap<ArrayKey, F> instanceCache = new ConcurrentHashMap<>(7);

  111.     /**
  112.      * Clears the cache.
  113.      */
  114.     void clearInstance() {
  115.         instanceCache.clear();
  116.     }

  117.     /**
  118.      * Create a format instance using the specified pattern, time zone
  119.      * and locale.
  120.      *
  121.      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
  122.      * @param timeZone  time zone, this will not be null.
  123.      * @param locale  locale, this will not be null.
  124.      * @return a pattern based date/time formatter
  125.      * @throws IllegalArgumentException if pattern is invalid
  126.      *  or {@code null}
  127.      */
  128.     protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale);

  129.     /**
  130.      * Gets a date formatter instance using the specified style,
  131.      * time zone and locale.
  132.      *
  133.      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
  134.      * @param timeZone  optional time zone, overrides time zone of
  135.      *  formatted date, null means use default Locale
  136.      * @param locale  optional locale, overrides system locale
  137.      * @return a localized standard date/time formatter
  138.      * @throws IllegalArgumentException if the Locale has no date/time
  139.      *  pattern defined
  140.      */
  141.     // package protected, for access from FastDateFormat; do not make public or protected
  142.     F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) {
  143.         return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
  144.     }

  145.     /**
  146.      * Gets a date/time formatter instance using the specified style,
  147.      * time zone and locale.
  148.      *
  149.      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
  150.      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
  151.      * @param timeZone  optional time zone, overrides time zone of
  152.      *  formatted date, null means use default Locale
  153.      * @param locale  optional locale, overrides system locale
  154.      * @return a localized standard date/time formatter
  155.      * @throws IllegalArgumentException if the Locale has no date/time
  156.      *  pattern defined
  157.      */
  158.     // package protected, for access from FastDateFormat; do not make public or protected
  159.     F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
  160.         return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale);
  161.     }

  162.     /**
  163.      * Gets a date/time formatter instance using the specified style,
  164.      * time zone and locale.
  165.      *
  166.      * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
  167.      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
  168.      * @param timeZone  optional time zone, overrides time zone of
  169.      *  formatted date, null means use default Locale
  170.      * @param locale  optional locale, overrides system locale
  171.      * @return a localized standard date/time formatter
  172.      * @throws IllegalArgumentException if the Locale has no date/time
  173.      *  pattern defined
  174.      */
  175.     // This must remain private, see LANG-884
  176.     private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
  177.         locale = LocaleUtils.toLocale(locale);
  178.         final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
  179.         return getInstance(pattern, timeZone, locale);
  180.     }

  181.     /**
  182.      * Gets a formatter instance using the default pattern in the
  183.      * default time zone and locale.
  184.      *
  185.      * @return a date/time formatter
  186.      */
  187.     public F getInstance() {
  188.         return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
  189.     }

  190.     /**
  191.      * Gets a formatter instance using the specified pattern, time zone
  192.      * and locale.
  193.      *
  194.      * @param pattern  {@link java.text.SimpleDateFormat} compatible
  195.      *  pattern, non-null
  196.      * @param timeZone  the time zone, null means use the default TimeZone
  197.      * @param locale  the locale, null means use the default Locale
  198.      * @return a pattern based date/time formatter
  199.      * @throws NullPointerException if pattern is {@code null}
  200.      * @throws IllegalArgumentException if pattern is invalid
  201.      */
  202.     public F getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
  203.         Objects.requireNonNull(pattern, "pattern");
  204.         final TimeZone actualTimeZone = TimeZones.toTimeZone(timeZone);
  205.         final Locale actualLocale = LocaleUtils.toLocale(locale);
  206.         final ArrayKey key = new ArrayKey(pattern, actualTimeZone, actualLocale);
  207.         return instanceCache.computeIfAbsent(key, k -> createInstance(pattern, actualTimeZone, actualLocale));
  208.     }

  209.     /**
  210.      * Gets a time formatter instance using the specified style,
  211.      * time zone and locale.
  212.      *
  213.      * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
  214.      * @param timeZone  optional time zone, overrides time zone of
  215.      *  formatted date, null means use default Locale
  216.      * @param locale  optional locale, overrides system locale
  217.      * @return a localized standard date/time formatter
  218.      * @throws IllegalArgumentException if the Locale has no date/time
  219.      *  pattern defined
  220.      */
  221.     // package protected, for access from FastDateFormat; do not make public or protected
  222.     F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
  223.         return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
  224.     }

  225. }