/*
 * 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.execution;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ant.antcore.antlib.AntLibDefinition;
import org.apache.ant.antcore.antlib.AntLibManager;
import org.apache.ant.antcore.antlib.AntLibrary;
import org.apache.ant.antcore.antlib.ComponentLibrary;
import org.apache.ant.antcore.antlib.DynamicLibrary;
import org.apache.ant.common.antlib.AntLibFactory;
import org.apache.ant.common.antlib.Converter;
import org.apache.ant.common.antlib.DeferredTask;
import org.apache.ant.common.antlib.ExecutionComponent;
import org.apache.ant.common.antlib.StandardLibFactory;
import org.apache.ant.common.antlib.Task;
import org.apache.ant.common.antlib.TaskContainer;
import org.apache.ant.common.event.MessageLevel;
import org.apache.ant.common.model.BuildElement;
import org.apache.ant.common.service.ComponentService;
import org.apache.ant.common.util.ExecutionException;
import org.apache.ant.common.util.Location;
import org.apache.ant.init.LoaderUtils;

/**
 * The instance of the ComponentServices made available by the core to the
 * ant libraries.
 *
 * @author Conor MacNeill
 * @created 27 January 2002
 */
public class ComponentManager implements ComponentService {
    /**
     * Type converters for this frame. Converters are used when configuring
     * Tasks to handle special type conversions.
     */
    private Map converters = new HashMap();

    /** This is the set of libraries whose converters have been loaded */
    private Set loadedConverters = new HashSet();

    /** The factory objects for each library, indexed by the library Id */
    private Map libFactories = new HashMap();

    /** The Frame this service instance is working for */
    private Frame frame;

    /** The library manager instance used to configure libraries. */
    private AntLibManager libManager;

    /**
     * These are AntLibraries which have been loaded into this component
     * manager
     */
    private Map antLibraries;

    /** dynamic libraries which have been defined */
    private Map dynamicLibraries;

    /** The definitions which have been imported into this frame. */
    private Map definitions = new HashMap();

    /**
     * This map stores a list of additional paths for each library indexed
     * by the libraryId
     */
    private Map libPathsMap;

    /** Reflector objects used to configure Tasks from the Task models. */
    private Map setters = new HashMap();


    /**
     * Constructor
     *
     * @param frame the frame containing this context
     * @param allowRemoteLibs true if remote libraries can be loaded though
     *      this service.
     * @param configLibPaths the additional library paths specified in the
     *      configuration
     */
    protected ComponentManager(Frame frame, boolean allowRemoteLibs,
                               Map configLibPaths) {
        this.frame = frame;
        libManager = new AntLibManager(allowRemoteLibs);
        dynamicLibraries = new HashMap();
        libPathsMap = new HashMap(configLibPaths);
    }

    /**
     * Load a library or set of libraries from a location making them
     * available for use
     *
     * @param libLocation the file or URL of the library location
     * @param importAll if true all tasks are imported as the library is
     *      loaded
     * @exception ExecutionException if the library cannot be loaded
     */
    public void loadLib(String libLocation, boolean importAll)
         throws ExecutionException {
        try {
            Map librarySpecs = new HashMap();
            libManager.loadLibs(librarySpecs, libLocation);
            libManager.configLibraries(frame.getInitConfig(), librarySpecs,
                antLibraries, libPathsMap);

            if (importAll) {
                Iterator i = librarySpecs.keySet().iterator();
                while (i.hasNext()) {
                    String libraryId = (String)i.next();
                    importLibrary(libraryId);
                }
            }
        } catch (MalformedURLException e) {
            throw new ExecutionException("Unable to load libraries from "
                 + libLocation, e);
        }
    }

    /**
     * Experimental - define a new task
     *
     * @param taskName the name by which this task will be referred
     * @param factory the library factory object to create the task
     *      instances
     * @param loader the class loader to use to create the particular tasks
     * @param className the name of the class implementing the task
     * @exception ExecutionException if the task cannot be defined
     */
    public void taskdef(AntLibFactory factory, ClassLoader loader,
                        String taskName, String className)
         throws ExecutionException {
        defineComponent(factory, loader, ComponentLibrary.TASKDEF,
            taskName, className);
    }

    /**
     * Experimental - define a new type
     *
     * @param typeName the name by which this type will be referred
     * @param factory the library factory object to create the type
     *      instances
     * @param loader the class loader to use to create the particular types
     * @param className the name of the class implementing the type
     * @exception ExecutionException if the type cannot be defined
     */
    public void typedef(AntLibFactory factory, ClassLoader loader,
                        String typeName, String className)
         throws ExecutionException {
        defineComponent(factory, loader, ComponentLibrary.TYPEDEF,
            typeName, className);
    }

    /**
     * Add a library path for the given library
     *
     * @param libraryId the unique id of the library for which an additional
     *      path is being defined
     * @param libPath the library path (usually a jar)
     * @exception ExecutionException if the path cannot be specified
     */
    public void addLibPath(String libraryId, URL libPath)
         throws ExecutionException {
        List libPaths = (List)libPathsMap.get(libraryId);
        if (libPaths == null) {
            libPaths = new ArrayList();
            libPathsMap.put(libraryId, libPaths);
        }
        libPaths.add(libPath);

        // If this library already exists give it the new path now
        AntLibrary library = (AntLibrary)antLibraries.get(libraryId);
        if (library != null) {
            libManager.addLibPath(library, libPath);
        }
    }

    /**
     * Import a complete library into the current execution frame
     *
     * @param libraryId The id of the library to be imported
     * @exception ExecutionException if the library cannot be imported
     */
    public void importLibrary(String libraryId) throws ExecutionException {
        AntLibrary library = (AntLibrary)antLibraries.get(libraryId);
        if (library == null) {
            throw new ExecutionException("Unable to import library " + libraryId
                 + " as it has not been loaded");
        }
        for (Iterator i = library.getDefinitionNames(); i.hasNext(); ) {
            String defName = (String)i.next();
            importLibraryDef(library, defName, null);
        }
        addLibraryConverters(library);
    }

    /**
     * Import a single component from a library, optionally aliasing it to a
     * new name
     *
     * @param libraryId the unique id of the library from which the
     *      component is being imported
     * @param defName the name of the component within its library
     * @param alias the name under which this component will be used in the
     *      build scripts. If this is null, the components default name is
     *      used.
     * @exception ExecutionException if the component cannot be imported
     */
    public void importComponent(String libraryId, String defName,
                                String alias) throws ExecutionException {
        AntLibrary library = (AntLibrary)antLibraries.get(libraryId);
        if (library == null) {
            throw new ExecutionException("Unable to import component from "
                 + "library \"" + libraryId + "\" as it has not been loaded");
        }
        importLibraryDef(library, defName, alias);
        addLibraryConverters(library);
    }

    /**
     * Imports a component defined in a nother frame.
     *
     * @param relativeName the qualified name of the component relative to
     *      this execution frame
     * @param alias the name under which this component will be used in the
     *      build scripts. If this is null, the components default name is
     *      used.
     * @exception ExecutionException if the component cannot be imported
     */
    public void importFrameComponent(String relativeName, String alias)
         throws ExecutionException {
        ImportInfo definition
             = frame.getReferencedDefinition(relativeName);

        if (definition == null) {
            throw new ExecutionException("The reference \"relativeName\" does"
                 + " not refer to a defined component");
        }

        String label = alias;
        if (label == null) {
            label = frame.getNameInFrame(relativeName);
        }

        frame.log("Adding referenced component <" + definition.getLocalName()
             + "> as <" + label + "> from library \""
             + definition.getComponentLibrary().getLibraryId() + "\", class: "
             + definition.getClassName(), MessageLevel.MSG_DEBUG);
        definitions.put(label, definition);
    }

    /**
     * Create a component. The component will have a context but will not be
     * configured. It should be configured using the appropriate set methods
     * and then validated before being used.
     *
     * @param componentName the name of the component
     * @return the created component. The return type of this method depends
     *      on the component type.
     * @exception ExecutionException if the component cannot be created
     */
    public Object createComponent(String componentName)
         throws ExecutionException {
        return createComponent(componentName, null);
    }

    /**
     * Create a component given its class. The component will have a context
     * but will not be configured. It should be configured using the
     * appropriate set methods and then validated before being used.
     *
     * @param componentClass the component's class
     * @param factory the factory to create the component
     * @param loader the classloader associated with the component
     * @param addTaskAdapter whenther the returned component should be a
     *      task, potentially being wrapped in an adapter
     * @param componentName the name of the component type
     * @return the created component. The return type of this method depends
     *      on the component type.
     * @exception ExecutionException if the component cannot be created
     */
    public Object createComponent(AntLibFactory factory, ClassLoader loader,
                                  Class componentClass, boolean addTaskAdapter,
                                  String componentName)
         throws ExecutionException {
        return createComponent(loader, factory, componentClass,
            componentName, componentName, addTaskAdapter, null);
    }

    /**
     * Set the standard libraries (i.e. those which are independent of the
     * build files) to be used in this component manager
     *
     * @param standardLibs A collection of AntLibrary objects indexed by
     *      their libraryId
     * @exception ExecutionException if the components cannot be imported
     *      form the libraries fro which such importing is automatic.
     */
    protected void setStandardLibraries(Map standardLibs)
         throws ExecutionException {

        antLibraries = new HashMap(standardLibs);

        // go through the libraries and import all standard ant libraries
        for (Iterator i = antLibraries.keySet().iterator(); i.hasNext(); ) {
            String libraryId = (String)i.next();
            if (libraryId.startsWith(Constants.ANT_LIB_PREFIX)) {
                // standard library - import whole library
                importLibrary(libraryId);
            }
        }
    }

    /**
     * Get the collection ov converters currently configured
     *
     * @return A map of converter instances indexed on the class they can
     *      convert
     */
    protected Map getConverters() {
        return converters;
    }

    /**
     * Get the collection of Ant Libraries defined for this frame Gets the
     * factory object for the given library
     *
     * @param componentLibrary the compnent library for which a factory
     *      objetc is required
     * @return the library's factory object
     * @exception ExecutionException if the factory cannot be created
     */
    protected AntLibFactory getLibFactory(ComponentLibrary componentLibrary)
         throws ExecutionException {
        String libraryId = componentLibrary.getLibraryId();
        if (libFactories.containsKey(libraryId)) {
            return (AntLibFactory)libFactories.get(libraryId);
        }
        ExecutionContext context
             = new ExecutionContext(frame, null, Location.UNKNOWN_LOCATION);
        AntLibFactory libFactory = componentLibrary.getFactory(context);
        if (libFactory == null) {
            libFactory = new StandardLibFactory();
        }
        libFactories.put(libraryId, libFactory);
        return libFactory;
    }

    /**
     * Get an imported definition from the component manager
     *
     * @param name the name under which the component has been imported
     * @return the ImportInfo object detailing the import's library and
     *      other details
     */
    protected ImportInfo getDefinition(String name) {
        return (ImportInfo)definitions.get(name);
    }

    /**
     * Create a component from a build model
     *
     * @param model the build model representing the component and its
     *      configuration
     * @return the configured component
     * @exception ExecutionException if there is a problem creating or
     *      configuring the component
     */
    protected Object createComponent(BuildElement model)
         throws ExecutionException {
        String componentName = model.getType();
        return createComponent(componentName, model);
    }

    /**
     * Create a component.
     *
     * @param componentName the name of the component which is used to
     *      select the object type to be created
     * @param model the build model of the component. If this is null, the
     *      component is created but not configured.
     * @return the configured component
     * @exception ExecutionException if there is a problem creating or
     *      configuring the component
     */
    protected Object createComponent(String componentName, BuildElement model)
         throws ExecutionException {

        Location location = Location.UNKNOWN_LOCATION;
        if (model != null) {
            location = model.getLocation();
        }
        ImportInfo definition = getDefinition(componentName);
        if (definition == null) {
            throw new ExecutionException("There is no definition of the <"
                 + componentName + "> component");
        }
        String className = definition.getClassName();

        ComponentLibrary componentLibrary
             = definition.getComponentLibrary();
        boolean isTask = definition.getDefinitionType() == AntLibrary.TASKDEF;
        String localName = definition.getLocalName();
        try {
            ClassLoader componentLoader = componentLibrary.getClassLoader();
            Class componentClass
                 = Class.forName(className, true, componentLoader);
            AntLibFactory libFactory = getLibFactory(componentLibrary);
            return createComponent(componentLoader, libFactory, componentClass,
                componentName, localName, isTask, model);
        } catch (ClassNotFoundException e) {
            throw new ExecutionException("Class " + className
                 + " for component <" + componentName + "> was not found", e,
                location);
        } catch (NoClassDefFoundError e) {
            throw new ExecutionException("Could not load a dependent class ("
                 + e.getMessage() + ") for component " + componentName,
                e, location);
        } catch (ExecutionException e) {
            e.setLocation(model.getLocation(), false);
            throw e;
        }
    }

    /**
     * Import a single component from the given library
     *
     * @param library the library which provides the component
     * @param defName the name of the component in the library
     * @param alias the name to be used for the component in build files. If
     *      this is null, the component's name within its library is used.
     */
    protected void importLibraryDef(ComponentLibrary library, String defName,
                                    String alias) {
        String label = alias;
        if (label == null) {
            label = defName;
        }

        AntLibDefinition libDef = library.getDefinition(defName);
        frame.log("Adding component <" + defName + "> as <" + label
             + "> from library \"" + library.getLibraryId() + "\", class: "
             + libDef.getClassName(), MessageLevel.MSG_DEBUG);
        definitions.put(label, new ImportInfo(library, libDef));
    }

    /**
     * Gets the setter for the given class
     *
     * @param c the class for which the reflector is desired
     * @return the reflector
     */
    private Setter getSetter(Class c) {
        if (setters.containsKey(c)) {
            return (Setter)setters.get(c);
        }
        Setter setter = null;
        if (DeferredTask.class.isAssignableFrom(c)) {
            setter = new DeferredSetter();
        } else {
            ClassIntrospector introspector
                 = new ClassIntrospector(c, getConverters());
            setter = introspector.getReflector();
        }

        setters.put(c, setter);
        return setter;
    }

    /**
     * Create a component - handles all the variations
     *
     * @param loader the component's classloader
     * @param componentClass The class of the component.
     * @param componentName The component's name in the global context
     * @param addTaskAdapter whether the component should add a Task adapter
     *      to make this component a Task.
     * @param localName The name of the component within its library
     * @param model the BuildElement model of the component's configuration
     * @param factory the facrtory object used to create the component
     * @return the required component potentially wrapped in a wrapper
     *      object.
     * @exception ExecutionException if the component cannot be created
     */
    private Object createComponent(ClassLoader loader, AntLibFactory factory,
                                   Class componentClass, String componentName,
                                   String localName, boolean addTaskAdapter,
                                   BuildElement model)
         throws ExecutionException {
        // set the location to unknown unless we have a build model to use
        Location location = Location.UNKNOWN_LOCATION;
        if (model != null) {
            location = model.getLocation();
        }

        try {
            // create the component using the factory
            Object component
                 = factory.createComponent(componentClass, localName);

            // wrap the component in an adapter if required.
            ExecutionComponent execComponent = null;
            if (addTaskAdapter) {
                if (component instanceof Task) {
                    execComponent = (Task)component;
                } else {
                    execComponent = new TaskAdapter(componentName, component);
                }
            } else if (component instanceof ExecutionComponent) {
                execComponent = (ExecutionComponent)component;
            }

            // set the context loader to that for the component
            ClassLoader currentLoader
                 = LoaderUtils.setContextLoader(loader);

            // if the component is an execution component create a context and
            // initialise the component with it.
            if (execComponent != null) {
                ExecutionContext context
                     = new ExecutionContext(frame, execComponent, location);
                context.setClassLoader(loader);
                execComponent.init(context, componentName);
            }

            // if we have a model, use it to configure the component. Otherwise
            // the caller is expected to configure thre object
            if (model != null) {
                configureElement(factory, component, model);
                // if the component is an execution component and we have a
                // model, validate it
                if (execComponent != null) {
                    execComponent.validateComponent();
                }
            }

            // reset the loader
            LoaderUtils.setContextLoader(currentLoader);

            // if we have an execution component, potentially a wrapper,
            // return it otherwise the component directly
            if (execComponent != null) {
                return execComponent;
            } else {
                return component;
            }
        } catch (InstantiationException e) {
            throw new ExecutionException("Unable to instantiate component "
                 + "class " + componentClass.getName() + " for component <"
                 + componentName + ">", e, location);
        } catch (IllegalAccessException e) {
            throw new ExecutionException("Unable to access task class "
                 + componentClass.getName() + " for component <"
                 + componentName + ">", e, location);
        } catch (ExecutionException e) {
            e.setLocation(location, false);
            throw e;
        } catch (RuntimeException e) {
            throw new ExecutionException(e.getClass().getName() + ": "
                 + e.getMessage(), e, location);
        }
    }

    /**
     * Create an instance of a type given its required class
     *
     * @param typeClass the class from which the instance should be created
     * @param model the model describing the required configuration of the
     *      instance
     * @param libFactory the factory object of the typeClass's Ant library
     * @param localName the name of the type within its Ant library
     * @return an instance of the given class appropriately configured
     * @exception ExecutionException if there is a problem creating the type
     *      instance
     */
    private Object createTypeInstance(Class typeClass, AntLibFactory libFactory,
                                      BuildElement model, String localName)
         throws ExecutionException {
        try {
            Object typeInstance
                 = libFactory.createComponent(typeClass, localName);

            if (typeInstance instanceof ExecutionComponent) {
                ExecutionComponent component = (ExecutionComponent)typeInstance;
                ExecutionContext context = new ExecutionContext(frame,
                    component, model.getLocation());
                component.init(context, localName);
                configureElement(libFactory, typeInstance, model);
                component.validateComponent();
            } else {
                configureElement(libFactory, typeInstance, model);
            }
            return typeInstance;
        } catch (InstantiationException e) {
            throw new ExecutionException("Unable to instantiate type class "
                 + typeClass.getName() + " for type <" + model.getType() + ">",
                e, model.getLocation());
        } catch (IllegalAccessException e) {
            throw new ExecutionException("Unable to access type class "
                 + typeClass.getName() + " for type <" + model.getType() + ">",
                e, model.getLocation());
        } catch (ExecutionException e) {
            e.setLocation(model.getLocation(), false);
            throw e;
        } catch (RuntimeException e) {
            throw new ExecutionException(e.getClass().getName() + ": "
                 + e.getMessage(), e, model.getLocation());
        }
    }

    /**
     * Create and add a nested element
     *
     * @param setter The Setter instance for the container element
     * @param element the container element in which the nested element will
     *      be created
     * @param model the model of the nested element
     * @param factory Ant Library factory associated with the element to
     *      which the attribute is to be added.
     * @exception ExecutionException if the nested element cannot be created
     */
    private void addNestedElement(AntLibFactory factory, Setter setter,
                                  Object element, BuildElement model)
         throws ExecutionException {
        String nestedElementName = model.getType();
        Class nestedType = setter.getType(nestedElementName);

        // is there a polymorph indicator - look in Ant aspects
        String typeName = model.getAspectValue(Constants.ANT_ASPECT, "type");
        String refId = model.getAspectValue(Constants.ANT_ASPECT, "refid");
        if (refId != null && typeName != null) {
            throw new ExecutionException("Only one of " + Constants.ANT_ASPECT
                 + ":type and " + Constants.ANT_ASPECT
                 + ":refid may be specified at a time", model.getLocation());
        }

        Object typeInstance = null;
        if (typeName != null) {
            // the build file has specified the actual type of the element.
            // we need to look up that type and use it
            typeInstance = createComponent(typeName, model);
        } else if (refId != null) {
            // We have a reference to an existing instance. Need to check if
            // it is compatible with the type expected by the nested element's
            // adder method
            typeInstance = frame.getDataValue(refId);
            if (model.getAttributeNames().hasNext() ||
                model.getNestedElements().hasNext() ||
                model.getText().length() != 0) {
                throw new ExecutionException("Element <" + nestedElementName
                     + "> is defined by reference and hence may not specify "
                     + "any attributes, nested elements or content",
                    model.getLocation());
            }
            if (typeInstance == null) {
                throw new ExecutionException("The given ant:refid value '"
                     + refId + "' is not defined", model.getLocation());
            }
        } else if (nestedType != null) {
            // We need to create an instance of the class expected by the nested
            // element's adder method if that is possible
            if (nestedType.isInterface()) {
                throw new ExecutionException("No element can be created for "
                     + "nested element <" + nestedElementName + ">. Please "
                     + "provide a value by reference or specify the value type",
                    model.getLocation());
            }
            typeInstance = createTypeInstance(nestedType, factory, model, null);
        } else {
            throw new ExecutionException("The type of the <"
                 + nestedElementName + "> nested element is not known. "
                 + "Please specify by the type using the \"ant:type\" "
                 + "attribute or provide a reference to an instance with "
                 + "the \"ant:id\" attribute");
        }

        // is the typeInstance compatible with the type expected
        // by the element's add method
        if (!nestedType.isInstance(typeInstance)) {
            if (refId != null) {
                throw new ExecutionException("The value specified by refId "
                     + refId + " is not compatible with the <"
                     + nestedElementName + "> nested element",
                    model.getLocation());
            } else if (typeName != null) {
                throw new ExecutionException("The type "
                     + typeName + " is not compatible with the <"
                     + nestedElementName + "> nested element",
                    model.getLocation());
            }
        }
        setter.addElement(element, nestedElementName, typeInstance);
    }

    /**
     * Create a nested element for the given object according to the model.
     *
     * @param setter the Setter instance of the container object
     * @param element the container object for which a nested element is
     *      required.
     * @param model the build model for the nestd element
     * @param factory Ant Library factory associated with the element
     *      creating the nested element
     * @exception ExecutionException if the nested element cannot be
     *      created.
     */
    private void createNestedElement(AntLibFactory factory, Setter setter,
                                     Object element, BuildElement model)
         throws ExecutionException {
        String nestedElementName = model.getType();
        try {
            Object nestedElement
                 = setter.createElement(element, nestedElementName);
            factory.registerCreatedElement(nestedElement);
            if (nestedElement instanceof ExecutionComponent) {
                ExecutionComponent component
                     = (ExecutionComponent)nestedElement;
                ExecutionContext context = new ExecutionContext(frame,
                    component, model.getLocation());
                component.init(context, nestedElementName);
                configureElement(factory, nestedElement, model);
                component.validateComponent();
            } else {
                configureElement(factory, nestedElement, model);
            }
        } catch (ExecutionException e) {
            e.setLocation(model.getLocation(), false);
            throw e;
        } catch (RuntimeException e) {
            throw new ExecutionException(e.getClass().getName() + ": "
                 + e.getMessage(), e, model.getLocation());
        }
    }


    /**
     * Configure an element according to the given model.
     *
     * @param element the object to be configured
     * @param model the BuildElement describing the object in the build file
     * @param factory Ant Library factory associated with the element being
     *      configured
     * @exception ExecutionException if the element cannot be configured
     */
    private void configureElement(AntLibFactory factory, Object element,
                                  BuildElement model)
         throws ExecutionException {
        Setter setter = getSetter(element.getClass());
        // start by setting the attributes of this element
        for (Iterator i = model.getAttributeNames(); i.hasNext(); ) {
            String attributeName = (String)i.next();
            String attributeValue = model.getAttributeValue(attributeName);
            if (!setter.supportsAttribute(attributeName)) {
                throw new ExecutionException(model.getType()
                     + " does not support the \"" + attributeName
                     + "\" attribute", model.getLocation());
            }
            setter.setAttribute(element, attributeName,
                frame.replacePropertyRefs(attributeValue));
        }

        String modelText = model.getText().trim();
        if (modelText.length() != 0) {
            if (!setter.supportsText()) {
                throw new ExecutionException(model.getType()
                     + " does not support content", model.getLocation());
            }
            setter.addText(element,
                frame.replacePropertyRefs(modelText));
        }

        // now do the nested elements
        for (Iterator i = model.getNestedElements(); i.hasNext(); ) {
            BuildElement nestedElementModel = (BuildElement)i.next();
            String nestedElementName = nestedElementModel.getType();
            ImportInfo info = getDefinition(nestedElementName);
            if (element instanceof TaskContainer
                 && info != null
                 && info.getDefinitionType() == AntLibrary.TASKDEF
                 && !setter.supportsNestedElement(nestedElementName)) {
                // it is a nested task
                Task nestedTask
                     = (Task)createComponent(nestedElementModel);
                TaskContainer container = (TaskContainer)element;
                container.addNestedTask(nestedTask);
            } else {
                if (setter.supportsNestedAdder(nestedElementName)) {
                    addNestedElement(factory, setter, element,
                        nestedElementModel);
                } else if (setter.supportsNestedCreator(nestedElementName)) {
                    createNestedElement(factory, setter, element,
                        nestedElementModel);
                } else {
                    throw new ExecutionException(model.getType()
                         + " does not support the \"" + nestedElementName
                         + "\" nested element",
                        nestedElementModel.getLocation());
                }
            }
        }
    }

    /**
     * Define a new component
     *
     * @param componentName the name this component will take
     * @param defType the type of component being defined
     * @param factory the library factory object to create the component
     *      instances
     * @param loader the class loader to use to create the particular
     *      components
     * @param className the name of the class implementing the component
     * @exception ExecutionException if the component cannot be defined
     */
    private void defineComponent(AntLibFactory factory, ClassLoader loader,
                                 int defType, String componentName,
                                 String className)
         throws ExecutionException {
        DynamicLibrary dynamicLibrary
             = new DynamicLibrary(factory, loader);
        dynamicLibrary.addComponent(defType, componentName, className);
        dynamicLibraries.put(dynamicLibrary.getLibraryId(), dynamicLibrary);
        importLibraryDef(dynamicLibrary, componentName, null);
    }


    /**
     * Add the converters from the given library to those managed by this
     * frame.
     *
     * @param library the library from which the converters are required
     * @exception ExecutionException if a converter defined in the library
     *      cannot be instantiated
     */
    private void addLibraryConverters(AntLibrary library)
         throws ExecutionException {
        if (!library.hasConverters()
             || loadedConverters.contains(library.getLibraryId())) {
            return;
        }

        String className = null;
        try {
            AntLibFactory libFactory = getLibFactory(library);
            ClassLoader converterLoader = library.getClassLoader();
            for (Iterator i = library.getConverterClassNames(); i.hasNext(); ) {
                className = (String)i.next();
                Class converterClass
                     = Class.forName(className, true, converterLoader);
                if (!Converter.class.isAssignableFrom(converterClass)) {
                    throw new ExecutionException("In Ant library \""
                         + library.getLibraryId() + "\" the converter class "
                         + converterClass.getName()
                         + " does not implement the Converter interface");
                }
                Converter converter
                     = libFactory.createConverter(converterClass);
                ExecutionContext context = new ExecutionContext(frame,
                    null, Location.UNKNOWN_LOCATION);
                converter.init(context);
                Class[] converterTypes = converter.getTypes();
                for (int j = 0; j < converterTypes.length; ++j) {
                    converters.put(converterTypes[j], converter);
                }
            }
            loadedConverters.add(library.getLibraryId());
        } catch (ClassNotFoundException e) {
            throw new ExecutionException("In Ant library \""
                 + library.getLibraryId() + "\" converter class "
                 + className + " was not found", e);
        } catch (NoClassDefFoundError e) {
            throw new ExecutionException("In Ant library \""
                 + library.getLibraryId()
                 + "\" could not load a dependent class ("
                 + e.getMessage() + ") for converter " + className);
        } catch (InstantiationException e) {
            throw new ExecutionException("In Ant library \""
                 + library.getLibraryId()
                 + "\" unable to instantiate converter class "
                 + className, e);
        } catch (IllegalAccessException e) {
            throw new ExecutionException("In Ant library \""
                 + library.getLibraryId()
                 + "\" unable to access converter class "
                 + className, e);
        }
    }
}

