blob: 46172681569e6d65a306073d4c044e813ef3713e [file] [log] [blame]
/*
* 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 {
}
}