package org.apache.tomcat.maven.common.run;

/*
 * 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.
 */

import org.apache.catalina.loader.WebappLoader;
import org.apache.maven.plugin.logging.Log;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * A {@linkplain WebappLoader} implementation that allows scanning for changes to project classpath in support of
 * context reloads.
 *
 * @author Ryan Connolly
 * @version $Id: ExternalRepositoriesReloadableWebappLoader.java 1185186 2011-10-17 14:06:08Z olamy $
 * @since 2.0
 */
public class ExternalRepositoriesReloadableWebappLoader
    extends WebappLoader
{

    /**
     * Last modification times of all jar and class files.
     */
    private Map<String, Long> modificationTimeMap = new HashMap<String, Long>();

    private Log log;

    /**
     * Default Constructor.
     */
    public ExternalRepositoriesReloadableWebappLoader()
    {
        super();
    }


    /**
     * Convenience Constructor allows setting of a parent ClassLoader.
     *
     * @param parent the ClassLoader instance to set as this Loader's parent ClassLoader.
     */
    public ExternalRepositoriesReloadableWebappLoader( ClassLoader parent, Log log )
    {
        super( parent );
        this.log = log;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addRepository( String repository )
    {
        super.addRepository( repository );
        try
        {
            File file = new File( new URL( repository ).getPath().replaceAll( "%20", " " ) );
            if ( file.isDirectory() )
            {
                addClassDirectory( file );
            }
            else if ( file.isFile() && file.getName().endsWith( ".jar" ) )
            {
                addFile( file );
            }
        }
        catch ( MalformedURLException muex )
        {
            throw new RuntimeException( muex );
        }
    }

    /**
     * Tracks modification times of files in the given class directory.
     *
     * @param directory the File directory to track modification times for.
     */
    private void addClassDirectory( File directory )
    {
        for ( File file : directory.listFiles() )
        {
            if ( file.isDirectory() )
            {
                //remember also directory last modification time
                addFile( file );
                addClassDirectory( file );
            }
            else if ( file.isFile() )
            {
                addFile( file );
            }
        }
    }

    /**
     * Tracks last modification time of the given File.
     *
     * @param file the File for which to track last modification time.
     */
    private void addFile( File file )
    {
        modificationTimeMap.put( file.getAbsolutePath(), file.lastModified() );
    }

    /**
     * Check if {@link WebappLoader} says modified(), if not then check files from added repositories.
     */
    @Override
    public boolean modified()
    {
        boolean modified = super.modified();
        if ( !modified )
        {
            if ( log != null )
            {
                log.debug( "classPath scanning started at " + new Date().toString() );
            }
            for ( Map.Entry<String, Long> entry : modificationTimeMap.entrySet() )
            {
                String key = entry.getKey();
                File file = new File( key );
                if ( file.exists() )
                {
                    // file could be deleted.
                    Long savedLastModified = modificationTimeMap.get( key );
                    if ( file.lastModified() > savedLastModified )
                    {
                        modified = true;
                        modificationTimeMap.put( key, file.lastModified() );

                        // directory last modification time can change when some class,
                        // jar or subdirectory was added or deleted.
                        if ( file.isDirectory() )
                        {
                            addClassDirectory( file );
                        }
                    }
                }
            }
        }
        if ( log != null )
        {
            log.debug( "context " + modified + " at " + new Date().toString() );
        }
        return modified;
    }

}
