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

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import de.nece.ccrle.util.JarClassLoader;
import de.nece.ccrle.util.FileMonitor;
import de.nece.ccrle.util.FileChangeListener;
import java.util.Collection;
import java.util.logging.Logger;

/**
 * A repository consisting of class loaders whose classes are stored in jar
 * files residing on a
 * local disk. The class loaders contained in this repository are
 * instances of {@link de.nece.ccrle.util.JarClassLoader}.
 *
 * @author Greg Kohring
 */
public class LoaderLDR extends LocalDiskRepository implements LoaderRepository{

    private static Logger logger =
                       Logger.getLogger( LoaderLDR.class.getName() );
                                                                                
    /**
     * The suffix for jar files
     */
    public static final String SUFFIX = ".jar";

    private Map classLoaders = null;
    private Map fileToLoaderID = null;

    private File[] dirListing = null;
    private Vector listeners = null;

    /** @link dependency */
    /*# JarClassLoader lnkJarClassLoader; */

    /**
     * Constructs a new repository using the specified directory name.
     * If the directory pointed to by the specified directory name does 
     * not exist it will be created.
     * 
     * @param directoryName a <code>String</code> specifying the name of a
     *      directory to be used for the repository.
     */
    public LoaderLDR( String directoryName , boolean monitor) 
                                    throws FileNotFoundException {
        this( new File( directoryName ), monitor );
    }

    /**
     * Constructs a new repository using the specified directory.
     * If the specified directory does not exist it will be created.
     * 
     * @param directory a <code>File</code> specifying the
     *      directory to be used for the repository.
     * @throws IOException if an I/O error occurs while opening or
     *      creating the repository.
     */
    public LoaderLDR( File directory , boolean monitor ) 
                        throws FileNotFoundException {
        super( directory, true );

        init();
    }

    /**
     * Retrieves the class loader identified by the specified id.
     *
     * @param loaderID the id of the class loader to be returned
     * @return the <code>ClassLoader</code> for
     *      the specified loaderID or null if the specified id is not
     *      in use.
     */
    public ClassLoader getClassLoader( String loaderID ){
        return (ClassLoader) classLoaders.get( loaderID );
    }

    /**
     * Retrieves a collection of all class loaders in this
     * repository.
     *
     * @return a <code>Collection</code> containing 
     *      <code>ClassLoader</code>s for
     *      the components in this repository.
     */
    public Collection getClassLoaders(){
        return classLoaders.values();
    }

    /**
     * Retrieves the class loader identified by the specified key.
     *
     * @param key the key corresponding to the class loader to be returned.
     * @return the <code>ClassLoader</code> for
     *      the specified key or null if the specified id is not
     *      in use.
     * @throws ClassCastException if the specified key is not a 
     *      of the appropriate type for the repository.
     */
    public Object getItem( Object key ) {
        return getClassLoader( (String) key );
    }

    /**
     * Retrieves this repository's id for the specified class loader.
     *
     * @param loader the <code>ClassLoader</code> whose ID is to be returned.
     * @return a <code>String</code> containing this repository's id for
     *      the specified loader or <code>null</code> if the loader is not
     *      known to this repository.
     */
    public String getLoaderID( ClassLoader loader ){
        if ( loader instanceof JarClassLoader ){
            JarClassLoader jcl = (JarClassLoader) loader;
            String name = jcl.getJarPath();
            if ( classLoaders.containsKey( name ) ){
                return name;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * Not allowed.
     *
     * @throws UnsupportedOperationException if the application attempts to
     *      add a class loader to the repository
     */
    public boolean add( Object key, Object item ) {
        throw new UnsupportedOperationException();
    }

    /**
     * Not allowed.
     *
     * @throws UnsupportedOperationException if the application attempts to
     *      remove a class loader from the repository
     */
    public boolean remove( Object key ) throws IOException {
        throw new UnsupportedOperationException();
   }


    protected File[] listing() {

        // Find all jars and subdirectories

        final File dir = getDirectory();
        File[] dirListing = dir.listFiles( new FileFilter() {
            public boolean accept( File file ){
                if ( file.canRead() ){
                    if ( file.getName().endsWith( SUFFIX ) ){
                        return true;
                    } else if ( file.isDirectory() ){
                        return true;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        });

        return dirListing;
    }


    private void init() {

        classLoaders = new HashMap();
        fileToLoaderID = new HashMap();
        listeners = new Vector();
        dirListing = listing();

        for ( int i = 0; i < dirListing.length; i++ ){

            try {
                URL url = dirListing[i].toURI().toURL();

                ClassLoader parent = this.getClass().getClassLoader();

                JarClassLoader loader = 
                            new JarClassLoader( new URL[] {url}, parent );

                classLoaders.put( loader.getJarPath(), loader ) ;
                fileToLoaderID.put( dirListing[i], loader.getJarPath() ) ;

            } catch ( MalformedURLException mue ){
                logger.info( mue.getMessage() );
            }

        }


    }

    /**
     * Called when some of the entries in the repository have changed.
     */
    protected void changed( List files ) {

        Vector changedLoaders = new Vector();

        for ( Iterator fit = files.iterator(); fit.hasNext(); ){
            try {
                File file = (File) fit.next();
                URL url = file.toURI().toURL();

                // remove the old loader in case the directory making up
                // a loader has new jar files in it which would cause the
                // loader to return a new path name.
                String id = (String) fileToLoaderID.remove( file );
                if ( id != null ){
                    classLoaders.remove( id );
                }

                ClassLoader parent = this.getClass().getClassLoader();

                JarClassLoader loader = 
                            new JarClassLoader( new URL[] {url}, parent );

                classLoaders.put( loader.getJarPath(), loader ) ;
                fileToLoaderID.put( file, loader.getJarPath() );

                changedLoaders.add( loader );

            } catch ( MalformedURLException mue ){
                logger.info( mue.getMessage() );
            }
        }

        if ( !listeners.isEmpty() ){
            for  ( Iterator lit = listeners.iterator(); lit.hasNext(); ){
                RepositoryChangeListener rcl = (RepositoryChangeListener)
                                                                lit.next(); 
                rcl.changed( changedLoaders );
            }
        }
    }

    /**
     * Called when some entries have been added to the repository.
     */
    protected void added( List files ) {

        Vector addedLoaders = new Vector();

        for ( Iterator fit = files.iterator(); fit.hasNext(); ){
            try {
                File file = (File) fit.next();
                URL url = file.toURI().toURL();

                ClassLoader parent = this.getClass().getClassLoader();

                JarClassLoader loader = 
                            new JarClassLoader( new URL[] {url}, parent );


                classLoaders.put( loader.getJarPath(), loader ) ;
                fileToLoaderID.put( file, loader.getJarPath() );

                addedLoaders.add( loader );

            } catch ( java.net.MalformedURLException mue ){
                logger.info( mue.getMessage() );
            }
        }

        if ( !listeners.isEmpty() ){
            for  ( Iterator lit = listeners.iterator(); lit.hasNext(); ){
                RepositoryChangeListener rcl = (RepositoryChangeListener)
                                                                lit.next(); 
                rcl.added( addedLoaders );
            }
        }
    }

    /**
     * Called when some entries have been deleted from the repository.
     */
    protected void deleted( List files ) {

        Vector deletedLoaders = new Vector();

        for ( Iterator fit = files.iterator(); fit.hasNext(); ){
                File file = (File) fit.next();

                String id = (String) fileToLoaderID.remove( file );

                if ( id != null ){
                    Object obj = classLoaders.remove( id );
                    deletedLoaders.add( obj );
                }
        }

        if ( !listeners.isEmpty() ){
            for  ( Iterator lit = listeners.iterator(); lit.hasNext(); ){
                RepositoryChangeListener rcl = (RepositoryChangeListener)
                                                                lit.next(); 
                rcl.deleted( deletedLoaders );
            }
        }

    }

    public void addListener( RepositoryChangeListener fcl ) {
        listeners.add( fcl );
    }

    public void removeListener( RepositoryChangeListener fcl ) {
        listeners.remove( fcl );
    }
}
