/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 */

package de.nece.ccrle.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;


/**
 * A class loader for loading classes contained in a collection of jar files.
 * All classes loaded by this class loader must reside in Jar files.
 *
 * @author Greg Kohring
 */
public class JarClassLoader extends URLClassLoader {

    private static final int BUF_SIZE = 2048;

    private Vector directories = null;

    private static Logger logger =
                       Logger.getLogger( JarClassLoader.class.getName() );

    /**
     * Constructs a new Jar class loader from the specified URLs.
     * <p>
     * The specified URLs must point to jar files or directories containing jar
     * jar files.  Class files not packaged into jars will be ignored.
     * The URLs will be searched in the order specified for classes 
     * and resources after first searching the parent class
     * loader. 
     * <p>
     * Any URL ending with a '/' is assumed to refer to a 
     * directory on a locally mounted file system, while those ending 
     * with ".jar" may refer to
     * a Jar file on a local or remote system.  Directories will be
     * searched recursively for any jar files which
     * they might contain, i.e., for any filename entries ending in ".jar".
     * URLs with other endings will be ignored.
     *
     *
     * @throws SecurityException  if a security manager exists and its
     *      <code>checkCreateClassLoader</code> method doesn't allow 
     *      creation of a class loader.
     */
    public JarClassLoader( URL[] urls ){
        super( extractJars( urls ) );
        directories = extractDirectories( urls );
    }

    /**
     * Constructs a new Jar class loader from the specified files with
     * the specified parent classloader.
     * <p>
     * The specified URLs must point to jar files or directories containing jar
     * jar files.  Class files not packaged into jars will be ignored.
     * The URLs will be searched for classes 
     * and resources after first searching the parent class
     * loader. 
     * <p>
     * Any URL ending with a '/' is assumed to refer to a 
     * directory on a locally mounted file system, while those ending 
     * with ".jar" are assumed to refer to
     * a Jar file on a local or remote file system.  Directories will be 
     * searched recursively for any jar files which
     * they might contain (i.e., for any filename entries ending in ".jar").
     * URLs with other endings will be ignored.
     *
     * @throws SecurityException  if a security manager exists and its
     *      <code>checkCreateClassLoader</code> method doesn't allow 
     *      creation of a class loader.
     */
    public JarClassLoader( URL[] urls, ClassLoader parent ){
        super( extractJars( urls ), parent );
        directories = extractDirectories( urls );
    }

    // Searches the url list for jars.
    // Only the jar files are retained. Any directories are searched
    // for any jars they might contain.
    private static URL[] extractJars( URL[] urls ){

        Vector jars = new Vector();

        try{
            for ( int u = 0; u < urls.length; u++ ){

                URL url = urls[u];

                if ( url.getQuery() != null ) continue;

                String protocol = url.getProtocol();

                String path = url.getPath();

                if ( path.endsWith( ".jar" ) ){

                    jars.add( url );

                } else if ( protocol.equals( "file" ) ){

                    // get the local directory as a file object

                    File dir = new File( url.getFile() );

                    // Search the listing for any jars

                    File listing[] = dir.listFiles();

                    for ( int i = 0; i < listing.length; i++){
                        if ( listing[i].getName().endsWith( ".jar" ) ){
                            try{
                                jars.add( listing[i].toURI().toURL() );
                            } catch (  MalformedURLException murle ){
                                logger.info( murle.getMessage() );
                            }
                        } else if ( listing[i].isDirectory() ) {
                            URL[] subJars = extractJars( 
                               new URL[] { listing[i].toURI().toURL() } );
                            jars.addAll( Arrays.asList( subJars ) );
                        }
                    }
                }
            }
        } catch ( Exception e ){
            logger.fine( e.getMessage() );
        }

        URL[] allJars = new URL[ jars.size() ];
        int j = 0;
        for( Iterator it = jars.iterator(); it.hasNext(); ){
            allJars[j++] = (URL) it.next();
        }
        return allJars;
    }

    // Searches the url list for directoies.
    private Vector extractDirectories( URL[] urls ){

        Vector directories = new Vector();

        for ( int u = 0; u < urls.length; u++ ){

            URL url = urls[u];

            if ( url.getQuery() != null ) continue;

            String path = url.getPath();

            if ( path.endsWith( "/" ) ){
                directories.add( url );
            }
        }

        return directories;
    }

    /**
     * Overrides <code>loadClass</code> from <code>URLClassLoader</code>. 
     * <p> This method first
     * check with the security manager for permission to access the given
     * class before it is loaded. Throws a <code>SecurityException</code> if
     * the security manager does not allow access to the specified class.
     *
     * @param name the name of the class to be loaded.
     * @param resolve if <code>true</code> then resolve the class.
     * @return the resulting <code>Class</code> object.
     * @throws ClassNotFoundException if the specified class is not found by
     *      this class loader.
     * @throws SecurityException if an attempt is made to add this class to a
     *      package that contains classes that were signed by a different 
     *      set of certificates than this class, or if the class name 
     *      begins with "java.".
     */
    public final synchronized Class loadClass( String name, boolean resolve )
                                throws ClassNotFoundException
    {
        // Check if we have sufficient permission to access this package.

        SecurityManager sm = System.getSecurityManager();

        if (sm != null) {
            String cname = name.replace( '/', '.' );
            if ( cname.startsWith( "[" ) ) {
                int b = cname.lastIndexOf( '[' ) + 2;
                if ( b > 1 && b < cname.length() ) {
                    cname = cname.substring(b);
                }
            }
            int lastDotIndex = cname.lastIndexOf( '.' );
            if ( lastDotIndex != -1 ) {
                sm.checkPackageAccess( name.substring( 0, lastDotIndex ) );
            }
        }

        return super.loadClass( name, resolve );
    }

    /**
     * Appends the specified URL to the list of URLs to search for classes and
     * resources. Note, only URLs which point to jar files will be added to
     * the search path. Those pointing to directories will be searched
     * for any jar files which they might contain.
     * <p>
     * NOTE: This overrides {@link java.net.URLClassLoader#addURL(URL)}.
     *
     * @param url a candidate <code>URL</code> to be added to the search path.
     */
    protected void addURL( URL url ){

        URL[] urls = extractJars( new URL[] { url } );

        for ( int u = 0; u < urls.length; u++ ){
            super.addURL( urls[u] );
        }

        if ( url.getPath().endsWith( "/" ) ){
            directories.add( url );
        }
    }

    /**
     * Retrieves the path of jar file names being searched by this 
     * class loader.
     *
     * @return a <code>String</code> containing the names of all the
     *      jar files this class loader will search. The filenames in 
     *      the list are separated by a ":".
     */
    public String getJarPath() {
        URL[] urls = getURLs();
        String names = "";
        for ( int u = 0; u < urls.length; u++){
            if ( u > 0 ) names = names.concat( ":" );
            String[] elements = urls[u].getPath().split( "/" );
            names = names.concat( elements[ elements.length - 1 ] );
        }
        return names;
    }

    /**
     * Retrieves a list of directory URLs (if any) which were searched 
     * for jar files.
     *
     * @return a <code>List</code> containing all the directories which were
     *      searched for jar files. If no directories were specified, the 
     *      list will be empty.
     */
    public List getDirectoryURLs() {
        return directories;
    }

}
