/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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 org.bodington.servlet.template;

import java.io.File;
import java.net.*;
import java.util.Hashtable;

import org.apache.log4j.Logger;

/**
 * @author  bmb6jrm
 */
public class XmlTemplateClassLoader extends URLClassLoader
{
    private static Logger log = Logger.getLogger(XmlTemplateClassLoader.class);
    // indicates whether the class loader is the main classloader
    // or a delegated classloader
    private boolean is_delegated;
    
    // the delegated class has to be of same type as this
    // main class in order to access protected methods
    private XmlTemplateClassLoader delegated_class_loader;
    private int reload_count=0;
    
    private File directory;
    
    private Hashtable class_file_timestamps = new Hashtable();
    
    
    
    private static URL[] getURLList( File directory )
    {
	try
	{
	    String path = directory.getAbsolutePath();
	    path.replace( '\\', '/' );
	    if ( !path.endsWith( "/" ) )
		path = path + "/";
	    URL[] urls = new URL[1];
	    urls[0] = new URL( "file", "", path );
	    return urls;
	}
	catch ( MalformedURLException e )
	{
	    log.error( e.getMessage(), e );
	    return null;
	}
    }
    
    
    /** This public constructor is used to instantiate the main classloader
     * for a particular template directory.  The class loader instatiated may
     * delegate class loading to a URLClassLoader if a fresh class loader is
     * required.  */
    public XmlTemplateClassLoader( File directory, ClassLoader parent )
    {
	super( getURLList( directory ), parent );
	this.directory = directory;
	
	is_delegated = false;
	// use the private constructor that makes the class behave just like
	// a URLClassLoader.
	delegated_class_loader = new XmlTemplateClassLoader( this, parent );
	reload_count++;
    }
    
    
    
    private XmlTemplateClassLoader( XmlTemplateClassLoader main_classloader, ClassLoader parent )
    {
	super( getURLList( main_classloader.directory ), parent );
	is_delegated = true;
	this.directory = main_classloader.directory;
	// the delegated class loader is a stand alone class with no parent
	delegated_class_loader = null;
    }
    
    public File getClassFile( String name )
    {
	String class_file_name;
	class_file_name = name.replace( '.', File.separatorChar ) + ".class";
	File class_file = new File( directory, class_file_name );
	if ( class_file.exists() && class_file.isFile() )
	    return class_file;
	return null;
    }
    
    
    private synchronized boolean isClassRecompiled( File class_file )
    {
	Long old_timestamp;
	long new_timestamp=0L;
	
	if ( class_file == null )
	    return false;
	
	old_timestamp = (Long)class_file_timestamps.get( class_file.getAbsolutePath() );
	new_timestamp = class_file.lastModified();

	return old_timestamp!=null && new_timestamp > old_timestamp.longValue();
    }
    
    public synchronized boolean isClassRecompiled( String name )
    {
	if ( name == null )
	    return false;
	return isClassRecompiled( getClassFile( name ) );
    }
    
    protected Class findClass(String name) throws ClassNotFoundException
    {
	return super.findClass( name );
    }
    
    protected synchronized Class loadClass( String name, boolean resolve )
    throws ClassNotFoundException
    {
	// a delegated class loader should just behave like a URLClassLoader
	if ( is_delegated )
	    return super.loadClass( name, resolve );
	
	
	// a main class loader should check timestamps and then delegate
	Class the_class;
	File class_file;
	
	
	class_file = getClassFile( name );
	if ( class_file == null )
	    throw new ClassNotFoundException( "Class not found: " + name );
	
	// if the class file has been loaded before but has been updated
	// since then we must create a new delegated class loader
	if ( isClassRecompiled( class_file ) )
	{
	    // use the private constructor that makes the class behave just like
	    // a URLClassLoader.
	    delegated_class_loader = new XmlTemplateClassLoader( this, getParent() );
	    reload_count++;
	}

	the_class = delegated_class_loader.loadClass( name, resolve );
	// record the new timestamp of the class file that was found.
	class_file_timestamps.put( class_file.getAbsolutePath(), new Long( class_file.lastModified() ) );
	
	return the_class;
    }
    
    
}
