All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.common.core.CalendarCache Maven / Gradle / Ivy

There is a newer version: 5.1.4
Show newest version
package net.snowflake.common.core;

import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.TimeZone;

/**
 * Created by mzukowski on 3/13/16.
 *
 * Implements a simple thread-local cache for GregorianCalendar objects.
 * Note, by default it is not enabled, and will re-create the objects.
 *
 */
public class CalendarCache
{
  private static class CalendarCacheState
  {
    HashMap map;

    boolean enabled;

    /**
     * If set, we will always produce clones of cached objects.
     * This still makes it faster than completely new object, but makes
     * objects safe for external operations that modify Calendar
     * (e.g. SimpleDateFormat can change the timezone).
     */
    boolean produceClones;

    /** If set, we always use Gregorian calendar, even before 1582 */
    boolean alwaysGregorian;

    public CalendarCacheState()
    {
      map = new HashMap();
      enabled = false;
      produceClones = true;
      alwaysGregorian = false;
    }
  };

  /** Thread local state */
  private static final ThreadLocal localState =
      new ThreadLocal()
      {
           @Override protected CalendarCacheState initialValue()
           {
             return new CalendarCacheState();
           }
      };

  /**
   * Enable or disable the cache
   * @param enabled true to enable cache or false
   */
  public static void setEnabled(boolean enabled)
  {
    localState.get().enabled = enabled;
  }

  /**
   * Configure the CalendarCache to produce clones of cached objects
   * (default behavior) or not.
   * It can be beneficial performance wise to not clone them, but it's
   * possibly dangerous as _some_ operations using calendars
   * (e.g. SimpleDateFormat) can modify the Calendar object.
   *
   * @param produceClones should it produce clones of cached objects.
   */
  public static void setProduceClones(boolean produceClones)
  {
    localState.get().produceClones = produceClones;
  }

  /**
   * Set if the the calendars produced should not observe Julian calendar
   * before 1582.
   *
   * Calling this function also clears the cache.
   */

  public static void setAlwaysGregorian(boolean alwaysGregorian)
  {
    localState.get().alwaysGregorian = alwaysGregorian;

    // Clear the cache, in case old values had a different setting
    localState.get().map.clear();
  }

  /**
   * Helper function.
   * Returns a GregorianCalendar object.
   * @param timezone  Timezone to create the calendar in.
   * @param tzId      The id of the timezone (e.g. "America/Los_Angeles")
   * @param calId     Explicit ID of the calendar. Can be used e.g.
   *                  if more than one copy of the calendar for the same
   *                  timezone is needed.
   *                  Note, if specified, the caller is responsible for
   *                  making sure it's always the same timezone.
   * @return  The GregorianCalendar object. Note, it can be reused by other
   *          consumers, so be careful changing any settings in it
   */
  public static GregorianCalendar get(TimeZone timezone,
                                      String tzId,
                                      String calId)
  {
    CalendarCacheState state = localState.get();
    GregorianCalendar res;
    if (state.enabled)
    {
      res = state.map.get(calId);
      if (res == null)
      { // Not found, create it
        res = createCalendar(timezone, tzId);
        // Save a fully-prepped entry in the cache
        state.map.put(calId, res);
      }
      else
      { // Found in cache

        // Verify it's the same timezone, just in case
        if (timezone != null & !res.getTimeZone().hasSameRules(timezone))
        {
          throw new IllegalArgumentException(
              "Cached timezone is not equivalent to the requested one:"
                  + timezone + "  VS  " + res.getTimeZone());
        }
        // Verify it's the same gregorian offset, just in case
        if (state.alwaysGregorian &&
            res.getGregorianChange().getTime() != Long.MIN_VALUE)
        {
          throw new IllegalArgumentException(
              "Cached calendar gregorian offset is not set as expected:"
                  + res.getGregorianChange().getTime() + "  VS  " + Long.MIN_VALUE);
        }
      }
      if (state.produceClones)
      {
        // Produce clones - for both newly created and previously cached calendars
        res = (GregorianCalendar)res.clone();
      }
      else
      {
        // Returning a possibly previously used calendar, clear it
        res.clear();
      }
      return res;
    }
    else
    {
      // Cache disabled, just create it
      return createCalendar(timezone, tzId);
    }
  }

  /** Create a properly initialized GregorianCalendar */
  private static GregorianCalendar createCalendar(TimeZone timezone, String tzId)
  {
    if (timezone == null)
    {
      // If not specified, create a timezone from an id
      timezone = TimeZone.getTimeZone(tzId);
    }
    // Create a new calendar
    GregorianCalendar cal = new GregorianCalendar(timezone);
    // Clear it, seems sometimes needed
    cal.clear();
    // Set always gregorian if needed
    if (localState.get().alwaysGregorian)
    {
      cal.setGregorianChange(new Date(Long.MIN_VALUE));
    }
    return cal;
  }


  /**
   * Return a GregorianCalendar object identified by id. 
   * If caching is enabled and an object with the same id existed, it will be returned,
   * otherwise  a new object will be created.
   * @param timezone  Timezone to create the calendar in
   * @param calId     Explicit ID of the calendar. Can be used e.g.
   *                  if more than one copy of the calendar for the same
   *                  timezone is needed.
   *                  Note, if specified, the caller is responsible for
   *                  making sure it's always the same timezone.
   * @return  The GregorianCalendar object. Note, it can be reused by other
   *          consumers, so be careful changing any settings in it
   */
  public static GregorianCalendar get(TimeZone timezone, String calId)
  {
    return get(timezone, null, calId);
  }

  /**
   * Return a cached GregorianCalendar object. If doesn't exist, will be created.
   * @param timezone  Timezone to create the calendar in
   * @return  The GregorianCalendar object. Note, it can be reused by other
   *          consumers, so do not change any non-basic settings in it
   *          (like date).
   */
  public static GregorianCalendar get(TimeZone timezone)
  {
    // Use TimeZone id to lookup the Calendar
    return get(timezone, null, timezone.getID());
  }

  /**
   * Return a cached GregorianCalendar object. If doesn't exist, will be created.
   * @param tzId The id of the timezone (e.g. "America/Los_Angeles")
   * @return  The GregorianCalendar object. Note, it can be reused by other
   *          consumers, so do not change any non-basic settings in it
   *          (like date).
   */
  public static GregorianCalendar get(String tzId)
  {
    // Pass null as timezone - we'll create it when needed.
    // We use tzId also as a calendar ID.
    return get(null, tzId, tzId);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy