/* ======================================================================
   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.server;

import org.apache.log4j.Logger;

import org.bodington.util.*;

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


public class ServerClassLoader extends java.net.URLClassLoader
    {
    private static Logger log = Logger.getLogger(ServerClassLoader.class);
    private static Hashtable instances = new Hashtable();
    private static int next_serial;


    private int serial;
    private URL jarUrl;
    private File basejar;
    private long checked;
    private ClassLoader parent;
    
        
    public static synchronized ServerClassLoader getClassLoader( File file, ClassLoader parent )
        throws IllegalArgumentException
        {
        if ( file==null )
            throw new IllegalArgumentException( "Class loader needs a class path." );
        if ( !file.getName().endsWith( ".jar" ) )
            throw new IllegalArgumentException( "Class loader only accepts files named *.jar." );
            
        ServerClassLoader cloader = (ServerClassLoader)instances.get( file.getAbsolutePath() );
            
        // if we've never created a class loader for this jar file do so.
        if ( cloader==null )
            return newClassLoader( file, parent );
            
        return cloader;
        }

    private static File bestJarFile( File file )
        {
        String filename = file.getAbsolutePath();
        String archivename = filename.substring( 0, filename.length()-4 );
        
        log.debug( archivename );

        File jarFile;
        for ( int i=10; i>=1; i-- )
            {
            jarFile = new File( archivename + "." + i + ".jar" );
                
            if ( jarFile.exists() )
                return jarFile;
            }

        if ( file.exists() )
                return file;
                
        return null;
        }

    private static synchronized ServerClassLoader newClassLoader( File file, ClassLoader parent )
        {
        try
            {
            File jarFile = bestJarFile( file );
            
            log.debug( "Jar file to use: " + jarFile.getAbsolutePath() );
            
            if ( jarFile == null )
                throw new IllegalArgumentException( "Class loader can't start: jar file doesn't exist." );
                
            URL [] list = new URL[1];
            list[0] = new URL( "file", "", jarFile.getAbsolutePath() );
            
            ServerClassLoader cloader = new ServerClassLoader( list, parent, jarFile );
            
            if ( cloader!=null )
                instances.put( file.getAbsolutePath(), cloader );
            
            return cloader;
            }
        catch ( MalformedURLException urlex )
        {
            log.error( urlex.getMessage(), urlex );
        }
        return null;
        }
        
        
    private ServerClassLoader( URL[] jarList, ClassLoader parent, File basejar  )
        {
        super( jarList, parent );

        this.jarUrl = jarList[0];
        this.basejar = basejar;
        this.checked = System.currentTimeMillis();
        this.parent = parent;
        
        serial = next_serial++;
        }

     protected synchronized Class loadClass( String name, boolean resolve )
        throws ClassNotFoundException
        {
        ServerClassLoader newcloader;
        
        if ( !instances.contains( this ) )
            {
            newcloader = (ServerClassLoader)instances.get( basejar.getAbsolutePath() );
            return newcloader.loadClass( name, resolve );
            }


        //worth checking for updated jar file?
        long now = System.currentTimeMillis();
        if ( (now - checked) > 10000 )
            {
            checked = now;
            
            try
                {
                File best = bestJarFile( basejar );
                URL besturl = new URL( "file", "", best.getAbsolutePath() );
                
                // has jar file been updated?
                if ( !besturl.equals( jarUrl ) )
                    {
                    newcloader = newClassLoader( basejar, parent );
                    return newcloader.loadClass( name, resolve );
                    }
                }
            catch ( MalformedURLException urlex )
            {
                log.error( urlex.getMessage(), urlex );
            }
            }
            
        return super.loadClass( name, resolve );
        }

    public String toString()
        {
        return "ServerClassLoader instance " + serial;
        }
           
    public static void main( String[] args )
        {
        ServerClassLoader cloader;
        Class c;
        Object o;
        Vector v = new Vector();
        
        Object test = new ISSPasswordGenerator();
        
        ClassLoader normal=test.getClass().getClassLoader();
        
        System.out.println( "Normal class loader: " + normal );
        
        try
            {
            while ( true )
                {
            
                cloader = ServerClassLoader.getClassLoader( new File( "c:/temp/test.jar" ), normal );
                System.out.println(  cloader.toString() );
                
                c = cloader.loadClass( "Test" );
                System.out.println(  c.toString() );
                
                o = c.newInstance();
                System.out.println(  o.toString() );

                v.addElement( o );

                System.out.println(  "Loaded Objects..." );
                for ( Enumeration enumeration = v.elements(); enumeration.hasMoreElements(); )
                    {
                    o = enumeration.nextElement();
                    System.out.println(  o.toString() );
                    }
                System.out.println(  "Sleeping." );
                
                Thread.sleep( 10000 );
                }
            
            }
        catch ( Exception ex )
            {
            ex.printStackTrace();
            }
        
        }
    }

