/*
 * 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.configuration.reloading;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.commons.configuration.ConfigurationUtils;
import org.apache.commons.configuration.FileConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>A reloading strategy that will reload the configuration every time its
 * underlying file is changed.</p>
 * <p>This reloading strategy does not actively monitor a configuration file,
 * but is triggered by its associated configuration whenever properties are
 * accessed. It then checks the configuration file's last modification date
 * and causes a reload if this has changed.</p>
 * <p>To avoid permanent disc access on successive property lookups a refresh
 * delay can be specified. This has the effect that the configuration file's
 * last modification date is only checked once in this delay period. The default
 * value for this refresh delay is 5 seconds.</p>
 * <p>This strategy only works with FileConfiguration instances.</p>
 *
 * @author Emmanuel Bourg
 * @version $Id: FileChangedReloadingStrategy.java 1210646 2011-12-05 21:25:01Z oheger $
 * @since 1.1
 */
public class FileChangedReloadingStrategy implements ReloadingStrategy
{
    /** Constant for the jar URL protocol.*/
    private static final String JAR_PROTOCOL = "jar";

    /** Constant for the default refresh delay.*/
    private static final int DEFAULT_REFRESH_DELAY = 5000;

    /** Stores a reference to the configuration to be monitored.*/
    protected FileConfiguration configuration;

    /** The last time the configuration file was modified. */
    protected long lastModified;

    /** The last time the file was checked for changes. */
    protected long lastChecked;

    /** The minimum delay in milliseconds between checks. */
    protected long refreshDelay = DEFAULT_REFRESH_DELAY;

    /** A flag whether a reload is required.*/
    private boolean reloading;

    /** The Log to use for diagnostic messages */
    private Log logger = LogFactory.getLog(FileChangedReloadingStrategy.class);

    public void setConfiguration(FileConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void init()
    {
        updateLastModified();
    }

    public boolean reloadingRequired()
    {
        if (!reloading)
        {
            long now = System.currentTimeMillis();

            if (now > lastChecked + refreshDelay)
            {
                lastChecked = now;
                if (hasChanged())
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug("File change detected: " + getName());
                    }
                    reloading = true;
                }
            }
        }

        return reloading;
    }

    public void reloadingPerformed()
    {
        updateLastModified();
    }

    /**
     * Return the minimal time in milliseconds between two reloadings.
     *
     * @return the refresh delay (in milliseconds)
     */
    public long getRefreshDelay()
    {
        return refreshDelay;
    }

    /**
     * Set the minimal time between two reloadings.
     *
     * @param refreshDelay refresh delay in milliseconds
     */
    public void setRefreshDelay(long refreshDelay)
    {
        this.refreshDelay = refreshDelay;
    }

    /**
     * Update the last modified time.
     */
    protected void updateLastModified()
    {
        File file = getFile();
        if (file != null)
        {
            lastModified = file.lastModified();
        }
        reloading = false;
    }

    /**
     * Check if the configuration has changed since the last time it was loaded.
     *
     * @return a flag whether the configuration has changed
     */
    protected boolean hasChanged()
    {
        File file = getFile();
        if (file == null || !file.exists())
        {
            if (logger.isWarnEnabled() && lastModified != 0)
            {
                logger.warn("File was deleted: " + getName(file));
                lastModified = 0;
            }
            return false;
        }

        return file.lastModified() > lastModified;
    }

    /**
     * Returns the file that is monitored by this strategy. Note that the return
     * value can be <b>null </b> under some circumstances.
     *
     * @return the monitored file
     */
    protected File getFile()
    {
        return (configuration.getURL() != null) ? fileFromURL(configuration
                .getURL()) : configuration.getFile();
    }

    /**
     * Helper method for transforming a URL into a file object. This method
     * handles file: and jar: URLs.
     *
     * @param url the URL to be converted
     * @return the resulting file or <b>null </b>
     */
    private File fileFromURL(URL url)
    {
        if (JAR_PROTOCOL.equals(url.getProtocol()))
        {
            String path = url.getPath();
            try
            {
                return ConfigurationUtils.fileFromURL(new URL(path.substring(0,
                        path.indexOf('!'))));
            }
            catch (MalformedURLException mex)
            {
                return null;
            }
        }
        else
        {
            return ConfigurationUtils.fileFromURL(url);
        }
    }

    private String getName()
    {
        return getName(getFile());
    }

    private String getName(File file)
    {
        String name = configuration.getURL().toString();
        if (name == null)
        {
            if (file != null)
            {
                name = file.getAbsolutePath();
            }
            else
            {
                name = "base: " + configuration.getBasePath()
                       + "file: " + configuration.getFileName();
            }
        }
        return name;
    }
}
