/*
 ************************************************************************
 *
 * 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.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import de.nece.ccrle.util.Services;

import eu.gemss.components.providers.Provider;
import java.io.Serializable;
import de.nece.ccrle.util.HashRegistry;

/**
 * A registry for overseeing component provider instances.
 * <p>
 * The <code>ComponentRegistry</code> maintains a registry of services offered
 * by various <i>component providers</i>.
 * <p>
 * A component is a set of interfaces and/or abstract classes.
 * A component provider is a factory for creating instances of a component.
 * For simple components, one could put the services themselves directly
 * into the registry, however, for complex components this is problematic
 * since all components will be instantiated, even those which the application 
 * does not use.  In these cases, a practical approach is to extend 
 * the component
 * to include a <i>component provider</i> class which is
 * a lightweight factory whose main job is to provide information
 * about the <i>component provider</i> and the components it offers, as well
 * as
 * to instantiate the classes which implement the advertised components.
 *
 *
 * @author Greg Kohring
 */
public class ComponentRegistry extends HashRegistry implements Serializable {

    /**
     * Constructs a default <code>ComponentRegistrar</code> without any
     * initial categories.
     */
    public ComponentRegistry() {
    }

    /**
     * Constructs <code>ComponentRegistrar</code> using the supplied
     * categories.
     * A category is a <code>String</code> containing the fully qualified
     * pathname of the component's interface.
     * If the collection contains
     * duplicate categories, then only one category is registered.
     *
     * @param initialCategories a <code>Collection</code> containing
     *      <code>String</code>s used to define the initial categories.
     * @throws ClassCastException if the objects in the collection are
     *      not <code>String</code>s.
     */
    public ComponentRegistry( Collection initialCategories ) 
                throws ClassCastException{
        addCategories( initialCategories );
    }

    /**
     * Adds the collection of categories to the registry.
     * <p>
     * A category is a a <code>String</code> containing the fully qualified
     * pathname of the component's interface.  If the category
     * already exists, then the registry is not changed.
     *
     * @param categories a <code>Collection</code> of
     *      <code>String</code> objects used to define a category.
     * @throws ClassCastException if the objects in the collection are
     *      not <code>String</code>s.
     */
    public void addCategories( Collection categories ) 
                        throws ClassCastException{
        if ( categories != null ){
            Iterator catIt = categories.iterator();
            while ( catIt.hasNext() ) {
                String category = (String) catIt.next();
                add( category );
            }
        }
    }
                                                                                
    /**
     * Adds all component categories available to the specified 
     * <code>ClassLoader</code> to the registry. 
     * <p>
     * A category is a a <code>String</code> containing the fully qualified
     * pathname of the component's interface.
     *
     * @param loader the <code>ClassLoader</code> used to locate all available
     *      components.
     */
    public void addAllCategories( ClassLoader loader ) {
        Collection components = Services.findServices( loader );
        addCategories( components );
    }

    /**
     * Adds all component categories available to the default 
     * <code>ClassLoader</code>
     * to the registry. 
     * <p>
     * A category is a a <code>String</code> containing the fully qualified
     * pathname of the component's interface.
     *
     *
     */
    public void addAllCategories() {
        Collection components = Services.findServices();
        addCategories( components );
    }

    /**
     * Deregisters all component providers with the given class name from 
     * all component categories.
     *
     * @param providerClassName the class name of the provider to be 
     *      deregistered.
     */
    public void deregisterComponentProviderbyName( 
                                            String providerClassName ) {
        Iterator categories = categories().iterator();

        while ( categories.hasNext() ) {
            String category = (String) categories.next();
            deregisterComponentProviderbyName ( category, providerClassName );
        }
    }

    /**
     * Deregisters all component providers with the given class name from 
     * the given category. If the specified category does not exist, or
     * the specified provider is not registered under that category, then
     * the registry is not changed.
     *
     * @param category a <code>String</code> specifiying the category from
     *      which to remove the provider.
     * @param providerClassName a <code>String</code> specifiying the provider
     *      who is to be removed.
     * @return <code>true</code> if the registry was changed.
     */
    public boolean deregisterComponentProviderbyName( 
                                            String category,
                                            String providerClassName ) {
        boolean status = false;

        Set component = getItems( category );

        if ( component == null ) return status;

        Iterator providers = component.iterator();

        while ( providers.hasNext() ) {
            Object provider = providers.next();
            Class providerClass = provider.getClass();
            if ( providerClassName.equals( providerClass.getName() ) ) {
                component.remove( provider );
                status = true;
            }
        }

        return status;
    }

    /**
     * Searches the complete application class path for component providers 
     * offering components in one of the previously defined
     * categories.  The components thus located are then instantiated using
     * the default <code>ClassLoader</code> and added then registered.
     */

    public void registerAllComponentProviders() {

        Iterator categories = categories().iterator();

        while ( categories.hasNext() ) {
            String s = (String) categories.next();
            registerAllComponentProviders( s );
        }
    }

    /**
     * Searches the complete application class path for component providers 
     * offering components in the specified
     * category.  The component providers thus located are instantiated using
     * the default <code>ClassLoader</code> and then registered.
     * <p>
     * If the category does
     * not exist it is first added to the registry, then a search is made
     * for the component providers.
     *
     * @param category a <code>Class</code>object defining the category of
     * component providers to be registered.
     */
    public void registerAllComponentProviders( final String category ) {

            add( category );
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            registerAllComponentProviders( category, loader );
    }


    /**
     * Uses the given <code>ClassLoader</code> to search for component 
     * providers 
     * offering components in one of the previously defined
     * categories.  The component proviers thus located are instantiated using
     * the given <code>ClassLoader</code> and then registered.
     */

    public void registerAllComponentProviders( ClassLoader loader) {

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

        Iterator categories = categories().iterator();

        while ( categories.hasNext() ) {
            String category = (String) categories.next();
            registerAllComponentProviders( category, loader );
        }
    }


    /**
     * Uses the given <code>ClassLoader</code> to search for component 
     * providers 
     * offering components in the specified category.
     * The components thus located are instantiated using
     * the given <code>ClassLoader</code> and then registered.
     * <p>
     * If the category does
     * not exist it is first added to the registry, then a search is made
     * for the component providers.
     *
     * @param category a <code>Class</code>object defining the category of
     * service providers to be registered.
     */
    public void registerAllComponentProviders( String category,
                                    ClassLoader loader ) {

            add( category );

            Collection providers = Services.findProviders( category,
                                                                loader );

            Iterator it = providers.iterator();
            while ( it.hasNext() ) {
                Object provider = it.next();
                if ( provider instanceof Provider ){ 
                    try {
                        registerComponentProvider( (Provider) provider, 
                                                                category );
                    } catch ( IllegalArgumentException iae ) {
                        System.err.println( 
                            "Warning: Unable to register provider: " +
                                                iae.getMessage() );
                    }
                }
            }
    }

    /**
     * Registers a component provider under the given catagory. If this
     * provider was previously registered, then the old registration
     * is deleted and the new one is added. In this way it is possible to
     * dynamically update a component provider in a running system. 
     * <p> 
     * If the category does not exist in the registry, then it will be
     * added.
     *
     * @param provider the component provider
     * @param category a <code>Class</code> object defining the catagory 
     *      under which this component provider is to be registered.
     * @throws IllegalArgumentException if either the provider or category
     *      is <code>null</code>, or the provider does not support the component
     *      identified by the given category.
     */
    public void registerComponentProvider( Provider provider, String category ) 
            throws IllegalArgumentException {

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

        add( category );


        // Make sure the provider realy does provide this component

        if ( !category.equals( provider.getComponentName() ) ){
            throw new IllegalArgumentException( 
                    " category mismatch:  " +
                    category + " != " + provider.getComponentName() +
                    " for provider: " + provider.getProviderName()  );
        }


        // Check to see if this provider was previously registered.
        // If so, remove it, then insert it once again.

        Set cps = getItems( category );
        Iterator itCPs = cps.iterator();
        while ( itCPs.hasNext() ) {
            Object anCP = itCPs.next();
            String className = anCP.getClass().getName();
            if ( className.equals( provider.getClass().getName() ) ){
                cps.remove( anCP );
            }
        }

        cps.add( provider );
    }

    /**
     * Returns an <code>Iterator</code> containing all registered
     * component providers in the given category. 
     *
     * @param category the category to be retrieved from.
     * @return an <code>Iterator</code> containing service provider
     *      objects from the given category, or null if no such
     *      category is known.
     */
    public Iterator getComponentProviders( String category )
    {

        Set cps = getItems( category );

        if ( cps == null ){
            cps = new HashSet();
        }

        return cps.iterator();
    }

    /**
     * Searches the given category for a component provider class with
     * the given class name.
     *
     * @param category defines the component of interest.
     * @param providerClassName a fully qualified class name identifying the
     * component
     * @return an <code>Provider</code> implementing
     * the given service having the given class name, or <code>null</code> if
     * no such object has been registered or the category is unkown.
     *
     */
    public Provider getComponentProviderbyName( String category,
                                            String providerClassName )
    {

        Set cps = getItems( category );

        if ( cps == null ) {
            return null;
        } else {
            Iterator cp = cps.iterator();
            while ( cp.hasNext() ) {
                Provider provider = (Provider) cp.next();
                if ( providerClassName.equals(provider.getClass().getName())){
                    return (Provider) provider;
                }
            }
        }

        return null;
    }

    /**
     * Searches the entire registry for a component provider with
     * the given class name.
     *<p>
     *
     * @param providerClassName the fully qualified class name of the
     * service provider.
     * @return the first provider of the 
     *      given class name contained in the registry, or null if no 
     *      provider with the given name is found.
     *
     */
    public Provider getServiceProviderbyName( String providerClassName ){

        Iterator categories = categories().iterator();
        Provider provider = null;

        while ( categories.hasNext() ) {
            String component = (String) categories.next();
            provider = getComponentProviderbyName( component, 
                                                     providerClassName );
            break;
        }

        return provider;
    }
}

