/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.ant.antcore.antlib;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.ant.common.antlib.AntContext;
import org.apache.ant.common.antlib.AntLibFactory;
import org.apache.ant.common.util.ExecutionException;

/**
 * This class represents the Ant library.
 *
 * @author Conor MacNeill
 * @created 14 January 2002
 */
public class AntLibrary implements ComponentLibrary {
    /**
     * This is the globally unique name of this library. It uses the same
     * conventions as the Java package space - i.e. reverse order DNS names
     * This name is used when importing tasks from this library
     */
    private String libraryId;

    /** THe URL of the antlib.xml library spec which defines this library */
    private URL definitionURL;

    /**
     * The URLs to use when contructing a classloader for the components in
     * this library.
     */
    private List libraryURLs = new ArrayList();

    /** The list of converter classnames defined in this library */
    private List converterClassNames = new ArrayList();

    /** The class name of this library's factory class, if any */
    private String factoryClassName;

    /** The parent classloader to use when contructing classloaders */
    private ClassLoader parentLoader;

    /** The library which this library extends, if any */
    private AntLibrary extendsLibrary;

    /** Indicates if each Task Instance should use its own classloader */
    private boolean isolated = false;

    /** The classloader for this library if it can be reused */
    private ClassLoader loader = null;

    /** The definitions in the library */
    private Map definitions = null;

    /**
     * Constructor for the AntLibrary object
     *
     * @param spec the specification from which this library is created.
     */
    public AntLibrary(AntLibrarySpec spec) {
        this.libraryId = spec.getLibraryId();
        this.definitions = spec.getDefinitions();
        this.isolated = spec.isIsolated();
        this.converterClassNames.addAll(spec.getConverters());
        this.factoryClassName = spec.getFactory();
        this.definitionURL = spec.getLibraryURL();
    }

    /**
     * Sets the Library which this library extends
     *
     * @param extendsLibrary The new ExtendsLibrary value
     */
    public void setExtendsLibrary(AntLibrary extendsLibrary) {
        this.extendsLibrary = extendsLibrary;
    }

    /**
     * Sets the ParentLoader of the AntLibrary
     *
     * @param parentLoader The new ParentLoader value
     */
    public void setParentLoader(ClassLoader parentLoader) {
        this.parentLoader = parentLoader;
    }

    /**
     * Gets the libraryId of the AntLibrary
     *
     * @return the libraryId value
     */
    public String getLibraryId() {
        return libraryId;
    }

    /**
     * Gets the ClassLoader of the AntLibrary
     *
     * @return The ClassLoader value
     */
    public ClassLoader getClassLoader() {
        if (isolated) {
            return createLoader();
        } else if (loader == null) {
            loader = createLoader();
        }
        return loader;
    }

    /**
     * Gets the definitions (taskdefs and typedefs) of the AntLibrary
     *
     * @return an iterator over the definition names
     */
    public Iterator getDefinitionNames() {
        return definitions.keySet().iterator();
    }

    /**
     * Get the definition of a particular component
     *
     * @param definitionName the name of the component within the library
     * @return an AntLibDefinition instance with information about the
     *      component's definition
     */
    public AntLibDefinition getDefinition(String definitionName) {
        return (AntLibDefinition)definitions.get(definitionName);
    }

    /**
     * Gets an the converter class names of the AntLibrary
     *
     * @return an iterator over a list of String class names
     */
    public Iterator getConverterClassNames() {
        return converterClassNames.iterator();
    }

    /**
     * Get the URL to where the library was loaded from
     *
     * @return the library's URL
     */
    public URL getDefinitionURL() {
        return definitionURL;
    }


    /**
     * Gat an instance of a factory object for creating objects in this
     * library.
     *
     * @param context the context to use for the factory creation if
     *      required
     * @return an instance of the factory, or null if this library does not
     *      support a factory
     * @exception ExecutionException if the factory cannot be created
     */
    public AntLibFactory getFactory(AntContext context)
         throws ExecutionException {
        try {
            AntLibFactory libFactory = null;
            if (factoryClassName != null) {
                Class factoryClass = Class.forName(factoryClassName,
                    true, getClassLoader());
                libFactory
                     = (AntLibFactory)factoryClass.newInstance();
                libFactory.init(context);
            }
            return libFactory;
        } catch (ClassNotFoundException e) {
            throw new ExecutionException("Unable to create factory "
                 + factoryClassName + " for the \"" + libraryId
                 + "\" Ant library", e);
        } catch (NoClassDefFoundError e) {
            throw new ExecutionException("Could not load a dependent class ("
                 + e.getMessage() + ") to create the factory "
                 + factoryClassName + " for the \"" + libraryId
                 + "\" Ant library", e);
        } catch (InstantiationException e) {
            throw new ExecutionException("Unable to instantiate factory "
                 + factoryClassName + " for the \"" + libraryId
                 + "\" Ant library", e);
        } catch (IllegalAccessException e) {
            throw new ExecutionException("Unable to access factory "
                 + factoryClassName + " for the \"" + libraryId
                 + "\" Ant library", e);
        }
    }

    /**
     * Indicate whether this library has any converters defined
     *
     * @return true if any converters have been defined
     */
    public boolean hasConverters() {
        return !converterClassNames.isEmpty();
    }

    /**
     * Add a library to path to this AntLibrary definition
     *
     * @param libraryURL the URL to the library to be added
     */
    public void addLibraryURL(URL libraryURL) {
        libraryURLs.add(libraryURL);
    }


    /**
     * Create classloader which can be used to load the classes of this ant
     * library
     *
     * @return the classloader for this ant library
     */
    private ClassLoader createLoader() {
        ClassLoader ourParent
             = extendsLibrary == null ? parentLoader
             : extendsLibrary.getClassLoader();
        return new URLClassLoader((URL[])libraryURLs.toArray(new URL[0]),
            ourParent);
    }

}

