/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 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.core.xml;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import javax.xml.parsers.*;
import org.apache.ant.core.model.*;
import org.apache.ant.core.support.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Parses a project from an XML source using a SAX Parser.
 *
 * @author <a href="mailto:conor@apache.org">Conor MacNeill</a>
 */ 
public class XMLProjectParser {
    private Stack recursionCheck = new Stack();
    
    /**
     * The factory used to create SAX parsers.
     */
    private SAXParserFactory parserFactory;

    static private Location getLocation(Locator locator) {
        return new Location(locator.getSystemId(), locator.getLineNumber(),
                            locator.getColumnNumber());
    }                            

    /**
     * Parse a build file form the given URL.
     *
     * @param buildSource the URL from where the build source may be read.
     *
     * @throws SAXParseException if there is a problem parsing the build file.
     */
    public Project parseBuildFile(URL buildSource) 
            throws ConfigException {
        try {
            parserFactory = SAXParserFactory.newInstance();
            SAXParser saxParser = parserFactory.newSAXParser();
            XMLReader xmlReader = saxParser.getXMLReader();

            recursionCheck.push(buildSource);
            ProjectRootHandler projectRootHandler 
                = new ProjectRootHandler(buildSource, xmlReader);
            saxParser.parse(buildSource.toString(), projectRootHandler);
            return projectRootHandler.getProject();
        }
        catch (SAXParseException e) {
            throw new ConfigException(e.getMessage(), e, 
                                      new Location(buildSource.toString(), 
                                                   e.getLineNumber(), e.getColumnNumber()));
        }
        catch (NoProjectReadException e) {
            throw new ConfigException("No project defined in build source", e, 
                                      new Location(buildSource.toString()));
       }
        catch (ParserConfigurationException e) {
            throw new ConfigException("Unable to parse project: " + e.getMessage(), e, 
                                      new Location(buildSource.toString()));
        }
        catch (SAXException e) {
            throw new ConfigException("Unable to parse project: " + e.getMessage(), e, 
                                      new Location(buildSource.toString()));
        }
        catch (IOException e) {
            throw new ConfigException("Unable to parse project: " + e.getMessage(), e, 
                                      new Location(buildSource.toString()));
        }
    }
    
    /**
     * The root handler handles the start of parsing. This element looks for the 
     * root element which must be a project element. It then delegates handling of the
     * project element to a project handler from which it extracts the parsed project.
     */
    private class ProjectRootHandler extends RootHandler {
        /**
         * The project handler created to parse the project element.
         */
        ProjectHandler projectHandler;
        
        /**
         * Create a Root Handler.
         *
         * @param buildSource the URL containing the build definition
         * @param reader the XML parser.
         */
        public ProjectRootHandler(URL buildSource, XMLReader reader) {
            super(buildSource, reader);
        }

        /**
         * Start a new element in the root. This must be a project element
         * All other elements are invalid.
         *
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException if there is a parsing problem.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                  Attributes attributes) throws SAXParseException {
            if (qualifiedName.equals("project")) {
                projectHandler = new ProjectHandler(getXMLReader(), this, 
                                                    getLocator(), attributes, getSourceURL());
            } else {
                throw new SAXParseException("Build file should start with a <project> element not <" + 
                                             qualifiedName + ">", getLocator());
            }
        }
        
        /**
         * Get the project that has been parsed from the element
         *
         * @return the project that has been parsed from the build osurce
         * 
         * @throws NoProjectReadException thrown if no project was read in.
         */
        public Project getProject() throws NoProjectReadException {
            if (projectHandler == null) {
                throw new NoProjectReadException();
            }
            return projectHandler.getProject();
        }
    }
    
    /**
     * Root Handler for include elements.
     * 
     * Includes must contain either a project (which is being extended) or 
     * a fragment element which contains the fragment to be included.
     */
    private class IncludeRootHandler extends RootHandler {
        /**
         * The project into which the fragment is to be included.
         */
        private Project project;
        
        /**
         * Create an Include Root Handler.
         *
         * @param buildSource the URL containing the fragment definition
         * @param reader the XML parser.
         * @param project the project into which the fragment's elements will be included.
         */
        public IncludeRootHandler(URL buildSource, XMLReader reader, Project project) {
            super(buildSource, reader);
            this.project = project;
        }

        /**
         * Start a new element in the include root. This must be a project element
         * or a fragment element. All other elements are invalid.
         *
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException if there is a parsing problem.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                  Attributes attributes) throws SAXParseException {
            if (qualifiedName.equals("project") ||
                qualifiedName.equals("fragment")) {
                // if it is a fragment, it must have no attributes
                // any project attributes are ignored
                if (qualifiedName.equals("fragment") && attributes.getLength() != 0) {
                    throw new SAXParseException("<fragment> element may not have any attributes", 
                                                getLocator());
                }                                                
                new ProjectHandler(getXMLReader(), this, getLocator(), 
                                   getSourceURL(), project);
            } else {
                throw new SAXParseException("An included file should contain either a " +
                                            "<project> or <fragment> element and not a <" + 
                                            qualifiedName + "> element", getLocator());
            }
        }
    }
    
    /**
     * Element to parse the project element.
     *
     * The project handler creates a number of different handlers to which it
     * delegates processing of child elements.
     */
    private class ProjectHandler extends ElementHandler {
        /**
         * The project being parsed.
         */
        private Project project;
        
        /**
         * The sourceURL for the current content being added to the project.
         */
        private URL sourceURL;

        /**
         * Create a ProjectHandler to read in a complete project.
         * 
         * @param xmlReader the XML parser being used to parse the project element.
         * @param parent the parent element handler.
         * @param locator the SAX locator object used to associate elements with source
         *        locations.
         * @param attributes the project element's attributes.
         * @param projectSource the URL from which the XML source is being parsed.
         */
        public ProjectHandler(XMLReader xmlReader, ContentHandler parent,
                              Locator locator, Attributes attributes, URL projectSource) 
            throws SAXParseException {
                
            super(xmlReader, parent, locator);
            this.sourceURL = projectSource;                            
            project = new Project(projectSource, getLocation(locator));

            String base = null;
            String defaultTarget = null;
            String projectName = null;
            
            Map aspects = new HashMap();
            for (int i = 0; i < attributes.getLength(); ++i) {
                String attributeName = attributes.getQName(i);
                String attributeValue = attributes.getValue(i);
                if (attributeName.equals("base")) {
                    base = attributeValue;
                }
                else if (attributeName.equals("default")) {
                    defaultTarget = attributeValue;
                }
                else if (attributeName.equals("name")) {
                    projectName = attributeValue;
                }
                else if (attributeName.indexOf(":") != -1) {
                    // potential aspect attribute
                    aspects.put(attributeName, attributeValue);
                }
                else {
                    throw new SAXParseException("The attribute '" + attributeName + "' is not " + 
                                                "supported by the <project> element", getLocator());
                }
            }
            
            project.setDefaultTarget(defaultTarget);
            project.setBase(base);
            project.setName(projectName);
            project.setAspects(aspects);
        }
        
        /**
         * Create a Project handler for an included fragment. The elements
         * from the fragment are added to the given project.
         *
         * @param xmlReader the XML parser being used to parse the project element.
         * @param parent the parent element handler.
         * @param locator the SAX locator object used to associate elements with source
         *        locations.
         * @param includeSource the URL from which the XML source is being included.
         * @param project the project to which the included fragments elements are added.
         */
        public ProjectHandler(XMLReader xmlReader, ContentHandler parent,
                              Locator locator, URL includeSource, Project project) {
            super(xmlReader, parent, locator);
            this.sourceURL = includeSource;                            
            this.project = project;
        }
        
        /**
         * Start a new element in the project. Project currently handle the 
         * following elements
         * <ul>
         *   <li>import</li>
         *   <li>include</li>
         *   <li>target</li>
         * </ul>
         *
         * Everything else is treated as a task.
         *
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException if there is a parsing problem.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                 Attributes attributes) throws SAXParseException {
            if (qualifiedName.equals("import")) {
                ImportHandler importHandler 
                    = new ImportHandler(getXMLReader(), this, getLocator(), 
                                        attributes, sourceURL);
                try {                                        
                    project.importProject(importHandler.getImportName(), 
                                      importHandler.getImportedProject());
                }
                catch (ProjectModelException e) {
                    throw new SAXParseException(e.getMessage(), getLocator(), e);
                }
            }
            else if (qualifiedName.equals("include")) {
                IncludeHandler includeHandler 
                    = new IncludeHandler(getXMLReader(), this, getLocator(), 
                                         attributes, sourceURL, project);
            }
            else if (qualifiedName.equals("target")) {
                TargetHandler targetHandler 
                    = new TargetHandler(getXMLReader(), this, getLocator(), attributes);
                try {                    
                    project.addTarget(targetHandler.getTarget());
                }
                catch (ProjectModelException e) {
                    throw new SAXParseException(e.getMessage(), getLocator(), e);
                }
            }
            else {
                // everything else is a task
                TaskElementHandler taskElementHandler 
                    = new TaskElementHandler(getXMLReader(), this, getLocator(), 
                                      attributes, qualifiedName);
                project.addTask(taskElementHandler.getTaskElement());
            }
        }
    
        /**
         * Get the project that has been parsed from the XML source
         *
         * @return the project model of the parsed project.
         */
        public Project getProject() {
            return project;
        }
    }
    
    /**
     * The import handler handles the importing of one project into another.
     * 
     * The project to be imported is parsed with a new parser and then added to the
     * current project under the given import name
     */
    private class ImportHandler extends ElementHandler {
        /**
         * The attribute used to name the import.
         */
        static public final String IMPORT_NAME_ATTR = "name";
        
        /**
         * The attribute name used to locate the project to be imported.
         */
        static public final String IMPORT_SYSTEMID_ATTR = "project";
        
        /**
         * The project that has been imported.
         */
        private Project importedProject;
        
        /**
         * The name under which the project is being imported.
         */
        private String importName;
        
        /**
         * The systemId (URL) where the project is to be imported from.
         */
        private String projectSystemId;

        /**
         * Create an import handler to import a project.
         *
         * @param xmlReader the XML parser being used to parse the import element.
         * @param parent the parent element handler.
         * @param locator the SAX locator object used to associate elements with source
         *        locations.
         * @param attributes attributes of the import statement.
         * @param importingSource the URL of the importing source.
         */
        public ImportHandler(XMLReader xmlReader, ContentHandler parent,
                             Locator locator, Attributes attributes, URL importingSource) 
                throws SAXParseException {
            super(xmlReader, parent, locator);
            for (int i = 0; i < attributes.getLength(); ++i) {
                String attributeName = attributes.getQName(i);
                String attributeValue = attributes.getValue(i);
                if (attributeName.equals(IMPORT_NAME_ATTR)) {
                    importName = attributeValue;
                }
                else if (attributeName.equals(IMPORT_SYSTEMID_ATTR)) {
                    projectSystemId = attributeValue;
                }
                else {
                    throw new SAXParseException("Attribute " + attributeName + 
                                                " is not allowed in an <import> element", getLocator());
                }                                                    
            }
            
            if (importName == null) {
                throw new SAXParseException("Attribute " + IMPORT_NAME_ATTR + 
                                            " is required in an <import> element", getLocator());
            }
            
            if (projectSystemId == null) {
                throw new SAXParseException("Attribute " + IMPORT_SYSTEMID_ATTR + 
                                            " is required in an <import> element", getLocator());
            }

            // create a new parser to read this project relative to the 
            // project's URI
            try {
                URL importURL = new URL(importingSource, projectSystemId);
                SAXParser importSAXParser = parserFactory.newSAXParser();
                XMLReader importXMLReader = importSAXParser.getXMLReader();
    
                
                if (recursionCheck.contains(importURL)) {
                    throw new SAXParseException("Circular import detected when importing '" + 
                                                importURL + "'", getLocator());
                }
                recursionCheck.push(importURL);                                                
                ProjectRootHandler importRootHandler = new ProjectRootHandler(importURL, importXMLReader);
                importSAXParser.parse(importURL.toString(), importRootHandler);
                if (recursionCheck.pop() != importURL) {
                    throw new RuntimeException("Failure to pop expected element off recursion stack");
                }
                importedProject = importRootHandler.getProject();
            }
            catch (SAXParseException e) {
                throw e;
            }
            catch (NoProjectReadException e) {
                throw new SAXParseException("No project was imported from " + projectSystemId, 
                                            getLocator());
            }
            catch (MalformedURLException e) {
                throw new SAXParseException("Unable to import project from " + projectSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
            catch (ParserConfigurationException e) {
                throw new SAXParseException("Unable to parse project imported from " + projectSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
            catch (SAXException e) {
                throw new SAXParseException("Unable to parse project imported from " + projectSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
            catch (IOException e) {
                throw new SAXParseException("Error reading project imported from " + projectSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
        }
        
        /**
         * Import does not support nested elements. This method will always throw an
         * exception
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException always.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                 Attributes attributes) throws SAXParseException {
            // everything is a task
            throw new SAXParseException("<import> does not support nested elements", getLocator()); 
        }
        
        /**
         * Get the project imported.
         *
         * @return an imported Project.
         */
        public Project getImportedProject() {
            return importedProject;
        }
        
        /**
         * Get the name under which the project is imported.
         *
         * @return the import name of the project
         */
        public String getImportName() {
            return importName;
        }
    }

    /**
     * The include handler is used to read in included projects or
     * fragments into a project.
     */
    private class IncludeHandler extends ElementHandler {
        /**
         * The attribute name which identifies the fragment to be included
         */
        static public final String INCLUDE_SYSTEMID_ATTR = "fragment";
        
        /**
         * The system id of the fragment to be included.
         */
        private String includeSystemId;

        /**
         * Create an IncludeHandler to include an element into the
         * current project
         *       
         * @param xmlReader the XML parser being used to parse the include element.
         * @param parent the parent element handler.
         * @param locator the SAX locator object used to associate elements with source
         *        locations.
         * @param attributes attributes of the include statement.
         * @param includingSource the URL of the including source.
         * @param project the project into which the included elements are added.
         */
        public IncludeHandler(XMLReader xmlReader, ContentHandler parent,
                              Locator locator, Attributes attributes, URL includingSource,
                              Project project) 
                throws SAXParseException {
            super(xmlReader, parent, locator);
            for (int i = 0; i < attributes.getLength(); ++i) {
                String attributeName = attributes.getQName(i);
                String attributeValue = attributes.getValue(i);
                if (attributeName.equals(INCLUDE_SYSTEMID_ATTR)) {
                    includeSystemId = attributeValue;
                }
                else {
                    throw new SAXParseException("Attribute " + attributeName + 
                                                " is not allowed in an <include> element", getLocator());
                }                                                    
            }
            
            if (includeSystemId == null) {
                throw new SAXParseException("Attribute " + INCLUDE_SYSTEMID_ATTR + 
                                            " is required in an <include> element", getLocator());
            }

            // create a new parser to read this project relative to the 
            // project's URI
            try {
                URL includeURL = new URL(includingSource, includeSystemId);
                SAXParser includeSAXParser = parserFactory.newSAXParser();
                XMLReader includeXMLReader = includeSAXParser.getXMLReader();
    
                if (recursionCheck.contains(includeURL)) {
                    throw new SAXParseException("Circular include detected when including '" + 
                                                includeURL + "'", getLocator());
                }
                recursionCheck.push(includeURL);                                                
                IncludeRootHandler includeRootHandler 
                    = new IncludeRootHandler(includeURL, includeXMLReader, project);
                includeSAXParser.parse(includeURL.toString(), includeRootHandler);
                if (recursionCheck.pop() != includeURL) {
                    throw new RuntimeException("Failure to pop expected element off recursion stack");
                }
            }
            catch (SAXParseException e) {
                throw e;
            }
            catch (MalformedURLException e) {
                throw new SAXParseException("Unable to include " + includeSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
            catch (ParserConfigurationException e) {
                throw new SAXParseException("Unable to parse include " + includeSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
            catch (SAXException e) {
                throw new SAXParseException("Unable to parse include " + includeSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
            catch (IOException e) {
                throw new SAXParseException("Error reading include " + includeSystemId + 
                                            ": " + e.getMessage(), 
                                            getLocator());
            }
        }

        /**
         * Include does not support nested elements. This method will always throw an
         * exception
         *
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException always.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                 Attributes attributes) throws SAXParseException {
            // everything is a task
            throw new SAXParseException("<include> does not support nested elements", getLocator()); 
        }
        
    }

    /**
     * A TargetHandler process the Target element.
     */
    private class TargetHandler extends ElementHandler {
        /**
         * The target being configured.
         */
        private Target target;
        
        /**
         * Create a Target handler. Event element in a target is 
         * considered to be a task
         *
         * @param xmlReader the XML parser being used to parse the target element.
         * @param parent the parent element handler.
         * @param locator the SAX locator object used to associate elements with source
         *        locations.
         * @param attributes attributes of the target
         */
        public TargetHandler(XMLReader xmlReader, ContentHandler parent,
                             Locator locator, Attributes attributes) 
                throws SAXParseException {
            super(xmlReader, parent, locator);
            String targetName = null;
            String depends = null;
            Map aspects = new HashMap();
            for (int i = 0; i < attributes.getLength(); ++i) {
                String attributeName = attributes.getQName(i);
                String attributeValue = attributes.getValue(i);
                if (attributeName.equals("name")) {
                    targetName = attributeValue;
                }
                else if (attributeName.equals("depends")) {
                    depends = attributeValue;
                }
                else if (attributeName.indexOf(":") != -1) {
                    // potential aspect attribute
                    aspects.put(attributeName, attributeValue);
                }
                else {
                    throw new SAXParseException("The attribute '" + attributeName + "' is not " + 
                                                "supported by the <target> element", getLocator());
                }
            }
            if (targetName == null) {
                throw new SAXParseException("Targets must have a name attribute", locator);
            }
            target = new Target(getLocation(locator), targetName);
            target.setAspects(aspects);
            
            if (depends != null) {
                StringTokenizer tokenizer = new StringTokenizer(depends, ",");
                while (tokenizer.hasMoreTokens()) {
                    String dependency = tokenizer.nextToken();
                    target.addDependency(dependency);
                }
            }
        }

        /*
         * Process an element within this target. All elements within the target are 
         * treated as tasks.
         *
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException if there is a parsing problem.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                 Attributes attributes) throws SAXParseException {
            // everything is a task
            TaskElementHandler taskHandler 
                = new TaskElementHandler(getXMLReader(), this, getLocator(), 
                                                 attributes, qualifiedName);
            target.addTask(taskHandler.getTaskElement());
        }
        
        /**
         * Get the target parsed by this handler.
         *
         * @return the Target model object parsed by this handler.
         */
        public Target getTarget() {
            return target;
        }
    }

    /**
     * A TaskElementHandler parses the task elements of a build
     */
    private class TaskElementHandler extends ElementHandler  {
        /**
         * The task element being parsed by this handler.
         */
        private TaskElement taskElement;
        
        /**
         * Create a task element handler to parse a task element
         *
         * @param xmlReader the XML parser being used to parse the task element.
         * @param parent the parent element handler.
         * @param locator the SAX locator object used to associate elements with source
         *        locations.
         * @param attributes attributes of the task element
         * @param elementTagName the name of the task element.
         */
        public TaskElementHandler(XMLReader xmlReader, ContentHandler parent, Locator locator, 
                                          Attributes attributes, String elementTagName) {
            super(xmlReader, parent, locator);     
            taskElement 
                = new TaskElement(getLocation(locator), elementTagName);
                
            Map aspects = new HashMap();
            for (int i = 0; i < attributes.getLength(); ++i) {
                String attributeName = attributes.getQName(i);
                String attributeValue = attributes.getValue(i);
                if (attributeName.indexOf(":") != -1) {
                    // potential aspect attribute
                    aspects.put(attributeName, attributeValue);
                }
                else {
                    taskElement.addAttribute(attributeName, attributeValue);
                }
            }
            taskElement.setAspects(aspects);
        }
        
        /** 
         * Process a nested element of this task element. All nested elements
         * of a taskElement are themselves taskElements.
         *
         * @param uri The Namespace URI.
         * @param localName The local name (without prefix).
         * @param qualifiedName The qualified name (with prefix)
         * @param attributes The attributes attached to the element. 
         *
         * @throws SAXParseException if there is a parsing problem.
         */
        public void startElement(String uri, String localName, String qualifiedName,
                                 Attributes attributes) throws SAXParseException {
            // everything within a task element is also a task element
            TaskElementHandler nestedHandler 
                = new TaskElementHandler(getXMLReader(), this, getLocator(), 
                                                 attributes, qualifiedName);
            taskElement.addTaskElement(nestedHandler.getTaskElement());                                         
        }
        
        public void characters(char[] buf, int start, int end) throws SAXParseException {
            taskElement.addText(new String(buf, start, end));
        }

        /**
         * Get the task element being parsed by this handler.
         *
         * @return the TaskElement being parsed.
         */
        public TaskElement getTaskElement() {
            return taskElement;
        }
    }
    
    /**
     * A NoProjectReadException is used to indicate that a project
     * was not read from the particular source. This will happen
     * if the source is empty.
     */
    private class NoProjectReadException extends Exception {
    }
}

