/*
 ************************************************************************
 *
 * 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.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.URL;
import java.net.JarURLConnection;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.jar.JarEntry;
import java.util.logging.Logger;


/**
 * A simple implementation of a service provider look-up mechanism.
 * <p>
 * A <i>service</i> is a set of interfaces and/or abstract classes.
 * A <i>service provider</i> is a factory which 
 * instantiates the classes implementing the advertised services.
 * <p>
 * <code>ServiceFinder</code> locates and optionally instantiates the services
 * in the application's classpath. Since not
 * every service will be used in each application, it is important that 
 * services requiring complex initialization procedures be extended to
 * include a lightweight <i>service provider interface</i> class
 * which is quick to instantiate. Directly instantiating
 * a service class should be used only for lightweight services. 
 * <p>
 * For information on how to declare a service provider using JAR files, 
 * see the <a 
 * href="http://java.sun.com/j2se/1.4.1/docs/guide/jar/jar.html">JAR File
 * Specification</a>.
 *
 *
 * @author G.A. Kohring
 * @version 0.1
 */

public class Services{

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

    // Make sure nobody tries to instantiate this class.
    private Services(){}

    /**
     * Locates and instantiates the SPIs for the given service using the
     * system <code>ClassLoader</code>.
     *
     * @param service a <code>String</code> containing the fully qualified
     *      pathname of the service.
     * @return a <code>Collection</code> of SPIs for the given service
     */
    public static Collection findInstalledProviders( String service){
        ClassLoader scl = ClassLoader.getSystemClassLoader().getParent();
        return Services.findProviders( service, scl );
    }

    /**
     * Locates and instantiates the SPIs for the given service using the
     * current <code>ContextClassLoader</code>.
     *
     * @param service a <code>String</code> containing the fully qualified
     *      pathname of the service.
     * @return a <code>Collection</code> of SPIs for the given service
     */
    public static Collection findProviders( final String service ){
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        return Services.findProviders( service, tcl );
    }

    /**
     * Locates and instantiates the SPIs for the given service using the
     * user specified <code>ClassLoader</code>. If no SPIs offering
     * the desired service are found, then an empty iterator is returned.
     *
     * @param service a <code>String</code> containing the fully qualified
     *      pathname of the service.
     * @param loader a <code>ClassLoader</code> object used to load the SPIs.
     * @return an <code>Iterator</code> yielding a list of SPIs for the given
     * category.
     * @throws IllegalArgumentException if <code>loader == null</code>.
     */
    public static Collection findProviders( String service,  
                                                final ClassLoader loader){

        Vector spis = new Vector();

        if ( service == null ) return spis;

        if ( loader == null ){
            throw new IllegalArgumentException( "loader == null!" );
        }

        String resourceName = "META-INF/services/" + service;

        Enumeration enumURLs = null;
        try{
            enumURLs = loader.getResources( resourceName );
        } catch ( IOException ioe ) {
            logger.warning( ioe.getMessage() );
            return spis;
        }

        while( enumURLs.hasMoreElements() ) {

            URL services = (URL) enumURLs.nextElement();

            BufferedReader in = null;

            try {
                in = new BufferedReader( new InputStreamReader( 
                                                services.openStream() ) );
            } catch ( IOException ioe ) {
                logger.warning( ioe.getMessage() );
                continue;
            }

            String inputLine;

            try{
                while ( (inputLine = in.readLine() ) != null){

                    String spiClassName;
                    String nonComment;

                    int commentCharIndex =  inputLine.indexOf( '#' );
                    if ( commentCharIndex == 0 ){
                        continue;
                    } else if ( commentCharIndex != -1 ) {
                        nonComment = inputLine.substring( 0, commentCharIndex );
                        spiClassName = nonComment.trim();
                    } else{
                        spiClassName = inputLine.trim();
                    }

                    if ( !spiClassName.equals( "" ) ){
                        Object spi = null;
                        try {

                            Class spiClassDef = Class.forName( 
                                            spiClassName, true, loader );
                                spi = spiClassDef.newInstance();
                        } catch ( InstantiationException ie ) {
                                logger.warning( "Could not instantiate: " +
                                                    ie.getMessage() );
                                continue;
                        } catch ( IllegalAccessException iae ) {
                                logger.warning( "Illegal Access: " +
                                                    iae.getMessage() );
                                continue;
                        } catch ( ClassNotFoundException cnfe ) {
                                logger.warning( "Class Not Found: " +
                                                    cnfe.getMessage() );
                                continue;
                        }

                        if ( spi != null ){
                            spis.add( spi );
                        }
                    }
                }
            } catch ( IOException ioe ) {
                    logger.warning( ioe.getMessage() );
            } finally {
                try {
                    in.close();
                } catch ( IOException ioe ) {
                    logger.warning( ioe.getMessage() );
                }
            }

        }

        return spis;
    }

    /**
     * Locates all services supported by service providers available through 
     * the default <code>ClassLoader</code>. Normally, the default class
     * loader for a given thread covers the application class path, the
     * system class path and the extension class path.
     * <p>
     * A service is defined by the entries in a jar file's 
     * <code>META-INF/services</code> directory.
     *
     * @return a <code>Collection</code> of <code>String</code>s
     *      naming the available services.
     */
    public static Collection findServices(){
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        return Services.findServices( tcl );
    }

    /**
     * Locates all services supported by service providers available through 
     * the given <code>ClassLoader</code>. 
     * <p>
     * A service is defined by the entries in a jar file's 
     * <code>META-INF/services</code> directory.
     * <p>
     * <strong>NOTE:  </strong>
     * Because the normal Java Class Loading algorithm delegates to the parent
     * class loader before looking into its own search path, this method 
     * will normally
     * return all
     * the services the parent knows about as well. This leads to a recursion
     * which may go as far back as the bootstrap class loader. 
     *
     * @param loader a <code>ClassLoader</code> object used to locate the SPIs.
     * @return a <code>Collection</code> of <code>String</code>s
     *      naming the available services.
     */
    public static Collection findServices( final ClassLoader loader ){

        HashSet serviceList = new HashSet();
        Enumeration enumURLs = null;

        if ( loader == null ){
            throw new IllegalArgumentException( "loader == null!" );
        }

        String servicesDirName = "META-INF/services/";

        try{
            enumURLs = loader.getResources( servicesDirName );
        } catch ( IOException ioe ) {
            logger.warning( ioe.getMessage() );
            return serviceList;
        }

        while ( enumURLs.hasMoreElements() ) {

            URL servicesDir = (URL) enumURLs.nextElement();

            JarURLConnection juc = null;

            try {
                juc = (JarURLConnection) servicesDir.openConnection();
            } catch ( IOException ioe ){
                logger.warning( ioe.getMessage() );
                continue;
            }

            Enumeration entries = null;

            try{
                entries = juc.getJarFile().entries();
            } catch ( IOException ioe ){
                logger.warning( ioe.getMessage() );
                continue;
            }

            while ( entries.hasMoreElements() ){
                JarEntry je = (JarEntry) entries.nextElement();
                String entryName = je.getName();

                if ( entryName.startsWith( servicesDirName ) ){
                    String service = entryName.replaceFirst( 
                                                    servicesDirName, "" );

                    if ( !service.equals( "" ) ){
                            serviceList.add( service );
                    }
                }
            }

        }

        return serviceList;
    }


}
