de.jollyday.HolidayManager.java Source code

Java tutorial

Introduction

Here is the source code for de.jollyday.HolidayManager.java

Source

/**
 * Copyright 2010 Sven Diedrichsen 
 * 
 * Licensed 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 de.jollyday;

import java.net.URL;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.joda.time.LocalDate;
import org.joda.time.ReadableInterval;

import de.jollyday.configuration.ConfigurationProviderManager;
import de.jollyday.util.CalendarUtil;
import de.jollyday.util.ClassLoadingUtil;

/**
 * Abstract base class for all holiday manager implementations. Upon call of
 * getInstance method the implementing class will be read from the
 * jollyday.properties file and instantiated.
 * 
 * @author Sven Diedrichsen
 * @version $Id: $
 */
public abstract class HolidayManager {

    private static final Logger LOG = Logger.getLogger(HolidayManager.class.getName());
    /**
     * Configuration property for the implementing Manager class.
     */
    private static final String MANAGER_IMPL_CLASS_PREFIX = "manager.impl";
    /**
     * Signifies if caching of manager instances is enabled. If not every call
     * to getInstance will return a newly instantiated and initialized manager.
     */
    private static boolean managerCachingEnabled = true;
    /**
     * This map represents a cache for manager instances on a per country basis.
     */
    private static final Map<String, HolidayManager> MANAGER_CHACHE = new HashMap<String, HolidayManager>();

    /**
     * Caches the holidays for a given year and state/region.
     */
    private Map<String, Set<Holiday>> holidaysPerYear = new HashMap<String, Set<Holiday>>();
    /**
     * The configuration properties.
     */
    private Properties properties = new Properties();

    /**
     * Utility for calendar operations
     */
    protected CalendarUtil calendarUtil = new CalendarUtil();
    /**
     * Utility to load classes.
     */
    private static ClassLoadingUtil classLoadingUtil = new ClassLoadingUtil();
    /**
     * Manager for configuration providers. Delivers the jollyday configuration.
     */
    private static ConfigurationProviderManager configurationProviderManager = new ConfigurationProviderManager();

    /**
     * Returns a HolidayManager instance by calling getInstance(NULL) and thus
     * using the default locales country code. code.
     * 
     * @return default locales HolidayManager
     */
    public static final HolidayManager getInstance() {
        return getInstance((String) null);
    }

    /**
     * Returns a HolidayManager instance by calling getInstance(NULL,
     * Properties) and thus using the default locales country code and the
     * provided configuration properties.
     * 
     * @param properties
     *            configuration properties to use
     * @return default locales HolidayManager
     */
    public static final HolidayManager getInstance(Properties properties) {
        return getInstance((String) null, properties);
    }

    /**
     * Returns a HolidayManager for the provided country.
     * 
     * @param c
     *            Country
     * @return HolidayManager
     */
    public static final HolidayManager getInstance(final HolidayCalendar c) {
        return getInstance(c.getId());
    }

    /**
     * Returns a HolidayManager for the provided country with the provided
     * configuration properties.
     * 
     * @param properties
     *            the configuration properties
     * @param c
     *            Country
     * @return HolidayManager
     */
    public static final HolidayManager getInstance(final HolidayCalendar c, Properties properties) {
        return getInstance(c.getId(), properties);
    }

    /**
     * Creates an HolidayManager instance. The implementing HolidayManager class
     * will be read from the jollyday.properties file. If the calendar is NULL
     * or an empty string the default locales country code will be used.
     * 
     * @param calendar
     *            a {@link java.lang.String} object.
     * @return HolidayManager implementation for the provided country.
     */
    public static final HolidayManager getInstance(final String calendar) {
        return getInstance(calendar, null);
    }

    /**
     * Creates an HolidayManager instance. The implementing HolidayManager class
     * will be read from the configuration properties. If the calendar is NULL
     * or an empty string the default locales country code will be used.
     * 
     * @param properties
     *            the configuration properties
     * @param calendar
     *            a {@link java.lang.String} object.
     * @return HolidayManager implementation for the provided country.
     */
    public static final HolidayManager getInstance(final String calendar, Properties properties) {
        final String calendarName = prepareCalendarName(calendar);
        HolidayManager m = isManagerCachingEnabled() ? getFromCache(calendarName) : null;
        if (m == null) {
            m = createManager(calendarName, properties);
        }
        return m;
    }

    /**
     * Creates an HolidayManager instance. The implementing HolidayManager class
     * will be read from the jollyday.properties file. If the URL is NULL an
     * exception will be thrown.
     * 
     * @param url
     *            the URL to the calendar's file
     * @return HolidayManager implementation for the provided country.
     */
    public static final HolidayManager getInstance(final URL url) {
        return getInstance(url, null);
    }

    /**
     * Creates an HolidayManager instance. The implementing HolidayManager class
     * will be read from the jollyday.properties file. If the URL is NULL an
     * exception will be thrown.
     * 
     * @param properties
     *            the configuration properties
     * @param url
     *            the URL to the calendar's file
     * @return HolidayManager implementation for the provided country.
     */
    public static final HolidayManager getInstance(final URL url, Properties properties) {
        if (url == null) {
            throw new NullPointerException("Missing URL.");
        }
        HolidayManager m = isManagerCachingEnabled() ? getFromCache(url.toString()) : null;
        if (m == null) {
            m = createManager(url, properties);
        }
        return m;
    }

    /**
     * Creates a new <code>HolidayManager</code> instance for the country and
     * puts it to the manager cache.
     * 
     * @param calendar
     *            <code>HolidayManager</code> instance for the calendar
     * @return new
     */
    private static HolidayManager createManager(final String calendar, Properties properties) {
        if (LOG.isLoggable(Level.FINER)) {
            LOG.finer("Creating HolidayManager for calendar '" + calendar + "'. Caching enabled: "
                    + isManagerCachingEnabled());
        }
        Properties props = configurationProviderManager.getConfigurationProperties(properties);
        String managerImplClassName = readManagerImplClassName(calendar, props);
        HolidayManager m = instantiateManagerImpl(managerImplClassName);
        m.setProperties(props);
        m.init(calendar);
        if (isManagerCachingEnabled()) {
            putToCache(calendar, m);
        }
        return m;
    }

    /**
     * Reads the managers implementation class from the properties config file.
     * 
     * @param calendar
     *            the calendar name
     * @param props
     *            properties to read from
     * @return the manager implementation class name
     */
    private static String readManagerImplClassName(final String calendar, Properties props) {
        String managerImplClassName = null;
        if (calendar != null && props.containsKey(MANAGER_IMPL_CLASS_PREFIX + "." + calendar)) {
            managerImplClassName = props.getProperty(MANAGER_IMPL_CLASS_PREFIX + "." + calendar);
        } else if (props.containsKey(MANAGER_IMPL_CLASS_PREFIX)) {
            managerImplClassName = props.getProperty(MANAGER_IMPL_CLASS_PREFIX);
        } else {
            throw new IllegalStateException(
                    "Missing configuration '" + MANAGER_IMPL_CLASS_PREFIX + "'. Cannot create manager.");
        }
        return managerImplClassName;
    }

    /**
     * Instantiates the manager implementating class.
     * 
     * @param managerImplClassName
     *            the managers class name
     * @return the implementation class instantiated
     */
    private static HolidayManager instantiateManagerImpl(String managerImplClassName) {
        try {
            Class<?> managerImplClass = classLoadingUtil.loadClass(managerImplClassName);
            Object managerImplObject = managerImplClass.newInstance();
            return HolidayManager.class.cast(managerImplObject);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create manager class " + managerImplClassName, e);
        }
    }

    /**
     * Creates a new <code>HolidayManager</code> instance for the URL and puts
     * it to the manager cache.
     * 
     * @param url
     *            the URL to a file containing the calendar
     * @return the holiday manager initialized with the provided URL
     */
    private static HolidayManager createManager(final URL url, Properties properties) {
        if (LOG.isLoggable(Level.FINER)) {
            LOG.finer(
                    "Creating HolidayManager for URL '" + url + "'. Caching enabled: " + isManagerCachingEnabled());
        }
        Properties props = configurationProviderManager.getConfigurationProperties(properties);
        String managerImplClassName = readManagerImplClassName(null, props);
        HolidayManager m = instantiateManagerImpl(managerImplClassName);
        m.setProperties(props);
        m.init(url);
        if (isManagerCachingEnabled()) {
            putToCache(url.toString(), m);
        }
        return m;
    }

    /**
     * Handles NULL or empty country codes and returns the default locals
     * country codes for those. For all others the country code will be trimmed
     * and set to lower case letters.
     * 
     * @param calendar
     * @return trimmed and lower case country code.
     */
    private static String prepareCalendarName(String calendar) {
        if (calendar == null || "".equals(calendar.trim())) {
            calendar = Locale.getDefault().getCountry().toLowerCase();
        } else {
            calendar = calendar.trim().toLowerCase();
        }
        return calendar;
    }

    /**
     * Caches the manager instance for this country.
     * 
     * @param country
     * @param manager
     */
    private static void putToCache(final String country, final HolidayManager manager) {
        synchronized (MANAGER_CHACHE) {
            MANAGER_CHACHE.put(country, manager);
        }
    }

    /**
     * Tries to retrieve a manager instance from cache by country.
     * 
     * @param country
     * @return Manager instance for this country. NULL if none is cached yet.
     */
    private static HolidayManager getFromCache(final String country) {
        synchronized (MANAGER_CHACHE) {
            return MANAGER_CHACHE.get(country);
        }
    }

    /**
     * If true, instantiated managers will be cached. If false every call to
     * getInstance will create new manager. True by default.
     * 
     * @param managerCachingEnabled
     *            the managerCachingEnabled to set
     */
    public static void setManagerCachingEnabled(boolean managerCachingEnabled) {
        HolidayManager.managerCachingEnabled = managerCachingEnabled;
    }

    /**
     * <p>
     * isManagerCachingEnabled.
     * </p>
     * 
     * @return the managerCachingEnabled
     */
    public static boolean isManagerCachingEnabled() {
        return managerCachingEnabled;
    }

    /**
     * Clears the manager cache from all cached manager instances.
     */
    public static void clearManagerCache() {
        synchronized (MANAGER_CHACHE) {
            MANAGER_CHACHE.clear();
        }
    }

    /**
     * Calls isHoliday with JODA time object.
     * 
     * @param c
     *            a {@link java.util.Calendar} object.
     * @param args
     *            a {@link java.lang.String} object.
     * @return a boolean.
     */
    public boolean isHoliday(final Calendar c, final String... args) {
        return isHoliday(calendarUtil.create(c), args);
    }

    /**
     * Show if the requested date is a holiday.
     * 
     * @param c
     *            The potential holiday.
     * @param args
     *            Hierarchy to request the holidays for. i.e. args = {'ny'} ->
     *            New York holidays
     * @return is a holiday in the state/region
     */
    public boolean isHoliday(final LocalDate c, final String... args) {
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(c.getYear());
        for (String arg : args) {
            keyBuilder.append("_");
            keyBuilder.append(arg);
        }
        String key = keyBuilder.toString();
        if (!holidaysPerYear.containsKey(key)) {
            Set<Holiday> holidays = getHolidays(c.getYear(), args);
            holidaysPerYear.put(key, holidays);
        }
        return calendarUtil.contains(holidaysPerYear.get(key), c);
    }

    /**
     * Returns a set of all currently supported calendar codes.
     * 
     * @return Set of supported calendar codes.
     */
    public static Set<String> getSupportedCalendarCodes() {
        Set<String> supportedCalendars = new HashSet<String>();
        for (HolidayCalendar c : HolidayCalendar.values()) {
            supportedCalendars.add(c.getId());
        }
        return supportedCalendars;
    }

    /**
     * <p>
     * Getter for the field <code>properties</code>.
     * </p>
     * 
     * @return the configuration properties
     */
    protected Properties getProperties() {
        return properties;
    }

    /**
     * <p>
     * Setter for the field <code>properties</code>.
     * </p>
     * 
     * @param properties
     *            the configuration properties to set
     */
    protected void setProperties(Properties properties) {
        this.properties.putAll(properties);
    }

    /**
     * Returns the holidays for the requested year and hierarchy structure.
     * 
     * @param year
     *            i.e. 2010
     * @param args
     *            i.e. args = {'ny'}. returns US/New York holidays. No args ->
     *            holidays common to whole country
     * @return the list of holidays for the requested year
     */
    abstract public Set<Holiday> getHolidays(int year, String... args);

    /**
     * Returns the holidays for the requested interval and hierarchy structure.
     * 
     * @param interval
     *            the interval in which the holidays lie.
     * @param args
     *            a {@link java.lang.String} object.
     * @return list of holidays within the interval
     */
    abstract public Set<Holiday> getHolidays(ReadableInterval interval, String... args);

    /**
     * Initializes the implementing manager for the provided calendar.
     * 
     * @param calendar
     *            i.e. us, uk, de
     */
    abstract public void init(String calendar);

    /**
     * Initializes the implementing manager for the provided URL.
     * 
     * @param resource
     *            the URL, to a file containing the calendar
     *            <p style="color:red;font-style:italic">
     *            Note 1: This can be omitted, in which case the default
     *            behavior of loading from the classpath with a specific name
     *            will be used
     *            </p>
     *            <p style="color:red;font-style:italic">
     *            Note 2: If this parameter is not omitted, then it may be a
     *            path to a file (relative or absolute) or a URL spec (such as
     *            http://somehos/somefile, or ftp://somehost/somefile or
     *            whatever is supported by the URL Stream Handlers installed on
     *            the JVM)
     *            </p>
     * 
     */
    abstract public void init(final URL resource);

    /**
     * Returns the configured hierarchy structure for the specific manager. This
     * hierarchy shows how the configured holidays are structured and can be
     * retrieved.
     * 
     * @return Current calendars hierarchy
     */
    abstract public CalendarHierarchy getCalendarHierarchy();

}