LinkFinderVisitor.java
// $Id$
/*
* ====================================================================
* Copyright (c) 2002-2004, Christophe Labouisse 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.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*/
package net.ggtools.grand.ant;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import net.ggtools.grand.ant.taskhelpers.SubAntHelper;
import net.ggtools.grand.exceptions.DuplicateElementException;
import net.ggtools.grand.exceptions.GrandException;
import net.ggtools.grand.graph.Node;
import net.ggtools.grand.log.LoggerManager;
import org.apache.commons.logging.Log;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.types.Path;
/**
* A task visitor looking for links created by tasks like <code>ant</code>,
* <code>antcall</code>, etc.
*
* @author Christophe Labouisse
*/
public class LinkFinderVisitor extends ReflectTaskVisitorBase {
/**
* Field log.
*/
private static final Log LOG =
LoggerManager.getLog(LinkFinderVisitor.class);
/**
* Field aliases.
*/
private static final Map<String, String> ALIASES =
new HashMap<String, String>();
// Initialize the alias list
static {
ALIASES.put("runtarget", "antcall");
ALIASES.put("foreach", "antcall");
// TODO check those tasks.
ALIASES.put("antcallback", "antcall");
ALIASES.put("antfetch", "ant");
ALIASES.put("switch", "if");
ALIASES.put("trycatch", "if");
}
/**
* Field ANT_FILE_PROPERTY.
* (value is {@value #ANT_FILE_PROPERTY})
*/
private static final String ANT_FILE_PROPERTY = "ant.file";
/**
* Field ATTR_ANTFILE.
* (value is {@value #ATTR_ANTFILE})
*/
private static final String ATTR_ANTFILE = "antfile";
/**
* Field ATTR_DIR.
* (value is {@value #ATTR_DIR})
*/
private static final String ATTR_DIR = "dir";
/**
* Field ATTR_NAME.
* (value is {@value #ATTR_NAME})
*/
private static final String ATTR_NAME = "name";
/**
* Field ATTR_TARGET.
* (value is {@value #ATTR_TARGET})
*/
private static final String ATTR_TARGET = "target";
/**
* Field ATTR_VALUE.
* (value is {@value #ATTR_VALUE})
*/
private static final String ATTR_VALUE = "value";
/**
* Field BUILD_XML.
* (value is {@value #BUILD_XML})
*/
private static final String BUILD_XML = "build.xml";
/**
* Field PARAM_ELEMENT.
* (value is {@value #PARAM_ELEMENT})
*/
private static final String PARAM_ELEMENT = "param";
/**
* Field PROPERTY_ELEMENT.
* (value is {@value #PROPERTY_ELEMENT})
*/
private static final String PROPERTY_ELEMENT = "property";
/**
* Field graph.
*/
private AntGraph graph;
/**
* Field project.
*/
private final AntProject project;
/**
* Field startNode.
*/
private AntTargetNode startNode;
/**
* Constructor for LinkFinderVisitor.
* @param project AntProject
*/
public LinkFinderVisitor(final AntProject project) {
this.project = project;
}
/**
* Default action for unknown task. The default behavior is to recurse in
* the children to find a possible task.
*
* @param wrapper
* wrapper to check.
* @throws GrandException if an error occurs in visit()
* @see net.ggtools.grand.ant.ReflectTaskVisitorBase#defaultVisit(org.apache.tools.ant.RuntimeConfigurable)
*/
@Override
public final void defaultVisit(final RuntimeConfigurable wrapper)
throws GrandException {
for (RuntimeConfigurable child : Collections.list(wrapper.getChildren())) {
visit(child);
}
}
/**
* Method getAliasForTask.
* @param taskName String
* @return String
* @see net.ggtools.grand.ant.ReflectTaskVisitorBase#getAliasForTask(java.lang.String)
*/
@Override
public final String getAliasForTask(final String taskName) {
String result = ALIASES.get(taskName);
if (result == null) {
result = taskName;
}
return result;
}
/**
* Process the <code>ant</code> task. This method will find or create the
* destination node of the task, create an {@link AntTaskLink}and find the
* nested <code>property</code> nodes to set the link properties. Only
* <code>name</code>,<code>value</code> property nodes will be
* processed: the <code>file</code> property nodes will be ignored.
*
* The called node name will be either the plain <code>target</code>
* attribute value if it is located in the current build file or
* <code>[<em>target</em>]</code>.
*
* @param wrapper
* the wrapper to process.
* @throws DuplicateElementException
* if a duplicate node is created, should not happen.
*/
public final void reflectVisit_ant(final RuntimeConfigurable wrapper)
throws DuplicateElementException {
final Project antProject = project.getAntProject();
LOG.info("Processing Ant target in " + startNode.getName());
// Find the build file.
final String targetBuildDirectoryName =
(String) wrapper.getAttributeMap().get(ATTR_DIR);
String antFile = (String) wrapper.getAttributeMap().get(ATTR_ANTFILE);
if (antFile == null) {
antFile = BUILD_XML;
} else {
antFile = antProject.replaceProperties(antFile);
}
File targetBuildFile = new File(antFile);
if (!targetBuildFile.isAbsolute()) {
if (targetBuildDirectoryName == null) {
targetBuildFile = new File(antProject.getBaseDir(), antFile);
} else {
final String parentDirectoryName =
antProject.replaceProperties(targetBuildDirectoryName);
File parentDirectory = new File(parentDirectoryName);
if (!parentDirectory.isAbsolute()) {
parentDirectory = new File(antProject.getBaseDir(),
parentDirectoryName);
}
targetBuildFile = new File(parentDirectory, antFile);
}
}
if (!(targetBuildFile.exists() && targetBuildFile.isFile())) {
LOG.warn("Ant file " + targetBuildFile + " is missing");
}
final List<Object> targetElements = getTargetElementNames(wrapper);
final AntTaskLink[] links;
if (targetElements.size() > 0) {
links = new AntTaskLink[targetElements.size()];
int i = 0;
for (Object object : targetElements) {
links[i++] = createAntTaskLink(targetBuildFile,
wrapper.getElementTag(), (String) object);
}
} else {
links = new AntTaskLink[]{createAntTaskLink(targetBuildFile,
wrapper.getElementTag(),
(String) wrapper.getAttributeMap().get(ATTR_TARGET))};
}
// Look to params children.
addNestPropertiesParameters(wrapper, links, PROPERTY_ELEMENT);
}
/**
* @param wrapper RuntimeConfigurable
* @return List<Object>
*/
private List<Object> getTargetElementNames(final RuntimeConfigurable wrapper) {
final List<Object> targetElements = new ArrayList<Object>();
for (final RuntimeConfigurable child : Collections.list(wrapper.getChildren())) {
if ("target".equals(child.getElementTag())) {
final Map<String, Object> childAttributeMap =
child.getAttributeMap();
// name is supposed to be a string; however, since we are putting
// it in an object collection, there is no need to cast it as a
// String right now.
final Object name = childAttributeMap.get(ATTR_NAME);
if (name != null) {
targetElements.add(name);
}
}
}
return targetElements;
}
/**
* Process <code>antcall</code> and similar tasks. The method will create
* a link between the current start node and the node referenced by the
* <code>target</code> attribute creating it with the
* {@link Node#ATTR_MISSING_NODE}if no such node exists. It will then
* create an {@link AntTaskLink}link and look for nested <code>param</code>
* elements to set parameters to newly created link.
*
* @param wrapper
* wrapper to process.
* @throws DuplicateElementException
* if a duplicate node is created (should not happen).
*/
public final void reflectVisit_antcall(final RuntimeConfigurable wrapper)
throws DuplicateElementException {
LOG.info("Processing antcall target in " + startNode.getName());
final Project antProject = project.getAntProject();
final List<Object> targetElements = getTargetElementNames(wrapper);
final AntTaskLink[] links;
final String elementTag = wrapper.getElementTag();
if (targetElements.size() > 0) {
links = new AntTaskLink[targetElements.size()];
int i = 0;
for (Object object : targetElements) {
final String endNodeName =
antProject.replaceProperties((String) object);
final AntTargetNode endNode = findOrCreateNode(endNodeName);
LOG.debug("Creating link from " + startNode
+ " to " + endNodeName);
links[i++] = graph.createTaskLink(null,
startNode, endNode, elementTag);
}
} else {
final String endNodeName = antProject.replaceProperties((String)
wrapper.getAttributeMap().get(ATTR_TARGET));
final AntTargetNode endNode = findOrCreateNode(endNodeName);
LOG.debug("Creating link from " + startNode + " to " + endNodeName);
links = new AntTaskLink[]{graph.createTaskLink(null,
startNode, endNode, elementTag)};
}
// Look to params children.
addNestPropertiesParameters(wrapper, links, PARAM_ELEMENT);
}
/**
* Process <code>subant</code> task. Depending of the existence of the
* <code>genericantfile</code> attribute, this method will either create a
* special link holding a list of directories or a set of <i>ant taskish</i>
* links. During those creations, the end nodes will be created with
* the {@link Node#ATTR_MISSING_NODE}attribute if needed.
*
* @param wrapper
* wrapper to process.
* @throws DuplicateElementException
* if a duplicate node is created (should not happen).
*/
public final void reflectVisit_subant(final RuntimeConfigurable wrapper)
throws DuplicateElementException {
LOG.info("Processing subant target in " + startNode.getName());
final Project antProject = project.getAntProject();
// Configure the wrapper's proxy and get the configured task.
((Task) wrapper.getProxy()).maybeConfigure();
final Object proxy = wrapper.getProxy();
if (proxy instanceof SubAntHelper) {
final SubAntHelper helper = (SubAntHelper) proxy;
final Path buildPath = helper.getBuildpath();
final String antfile = helper.getAntfile();
final File genericantfile = helper.getGenericAntfile();
final Collection<Property> properties = helper.getProperties();
final String target = helper.getTarget();
final List<File> genericantfileDirs = new LinkedList<File>();
if ((buildPath == null) || (buildPath.size() == 0)) {
LOG.warn("buildPath is null or empty, subant task probably won't work");
return;
}
final String[] filenames = buildPath.list();
for (final String currentFileName : filenames) {
File directory = null;
File file = new File(currentFileName);
if (file.isDirectory()) {
if (genericantfile != null) {
directory = file;
file = genericantfile;
} else {
file = new File(file, antfile);
}
}
if (directory == null) {
// First case: antfile.
final AntTaskLink link =
createAntTaskLink(file, wrapper.getElementTag(),
target);
for (final Property property : properties) {
if (property.getName() != null) {
// Simple property
link.setParameter(property.getName(),
antProject.replaceProperties(property.getValue()));
} else if (property.getFile() != null) {
// Property file.
final File propFile = property.getFile();
if (LOG.isDebugEnabled()) {
LOG.debug("Loading " + propFile.getAbsolutePath());
}
link.addPropertyFile(propFile.getAbsolutePath());
}
}
} else {
// Second case, genericantfile, push the directory on a list
// to be used latter.
genericantfileDirs.add(directory);
}
}
if (genericantfileDirs.size() > 0) {
final AntTargetNode endNode =
findOrCreateNode(target, genericantfile);
LOG.debug("Creating link from " + startNode
+ " to " + endNode.getName());
final SubantTaskLink link = graph.createSubantTaskLink(null,
startNode, endNode, wrapper.getElementTag());
for (File currentDir : genericantfileDirs) {
link.addDirectory(currentDir.getAbsolutePath());
}
}
} else {
LOG.warn("Cannot get information for subant task");
LOG.debug("Task should be instance of SubAntHelper but is " + proxy);
}
}
/**
* @param graph
* The graph to set.
*/
public final void setGraph(final AntGraph graph) {
this.graph = graph;
}
/**
* @param startNode
* The startNode to set.
*/
public final void setStartNode(final AntTargetNode startNode) {
this.startNode = startNode;
}
/**
* Add to a given link the properties contained in an element.
*
* @param wrapper
* wrapper for the task.
* @param links AntTaskLink[]
* @param elementName
* name of the elements holding the properties.
*/
private void addNestPropertiesParameters(final RuntimeConfigurable wrapper,
final AntTaskLink[] links, final String elementName) {
final Project antProject = project.getAntProject();
for (final RuntimeConfigurable child : Collections.list(wrapper.getChildren())) {
if (elementName.equals(child.getElementTag())) {
final Map<String, Object> childAttributeMap =
child.getAttributeMap();
final String name = (String) childAttributeMap.get(ATTR_NAME);
if (name != null) {
final String propertyValue = antProject.replaceProperties((String)
childAttributeMap.get(ATTR_VALUE));
for (final AntTaskLink link : links) {
link.setParameter(name, propertyValue);
}
} else {
final String fileName = (String) childAttributeMap.get("file");
if (fileName != null) {
for (final AntTaskLink link : links) {
link.addPropertyFile(antProject.replaceProperties(fileName));
}
}
}
}
}
}
/**
* @param targetBuildFile File
* @param taskName String
* @param target String
* @return AntTaskLink
* @throws DuplicateElementException
* if there is already a link with the same name.
*/
private AntTaskLink createAntTaskLink(final File targetBuildFile, final String taskName,
final String target) throws DuplicateElementException {
final AntTargetNode endNode = findOrCreateNode(target, targetBuildFile);
LOG.debug("Creating link from " + startNode + " to " + endNode.getName());
return graph.createTaskLink(null, startNode, endNode, taskName);
}
/**
* @param endNodeName String
* @return AntTargetNode
* @throws DuplicateElementException
* if there is already a node with the same name.
*/
private AntTargetNode findOrCreateNode(final String endNodeName)
throws DuplicateElementException {
return findOrCreateNode(endNodeName, null);
}
/**
* @param target String
* @param targetBuildFile File
* @return AntTargetNode
* @throws DuplicateElementException
* if there is already a node with the same name.
*/
private AntTargetNode findOrCreateNode(final String target,
File targetBuildFile) throws DuplicateElementException {
final Project antProject = project.getAntProject();
final File projectFile = new File(antProject.getProperty(ANT_FILE_PROPERTY));
if (targetBuildFile == null) {
targetBuildFile = projectFile;
}
final boolean isSameBuildFile = projectFile.equals(targetBuildFile);
String endNodeName;
String targetName = antProject.replaceProperties(target);
AntTargetNode endNode;
if (isSameBuildFile) {
endNodeName = (targetName == null) ? antProject.getDefaultTarget() : targetName;
endNode = (AntTargetNode) graph.getNode(endNodeName);
} else {
if (targetName == null) {
try {
// TODO caching.
LOG.debug("Reading project file " + targetBuildFile);
final AntProject tmpProj = new AntProject(targetBuildFile);
targetName = tmpProj.getAntProject().getDefaultTarget();
} catch (final GrandException e) {
LOG.info("Caught exception trying to read " + targetBuildFile
+ " using default target name", e);
targetName = "'default'";
}
}
// Find out the "right" node avoiding conflicts.
// FIXME the current algorithm seems really bad, check if a cache is worth implementing.
int index = 1;
boolean conflict = false;
endNodeName = "[" + targetName + "]";
do {
conflict = false;
endNode = (AntTargetNode) graph.getNode(endNodeName);
if ((endNode != null)
&& !targetBuildFile.getAbsolutePath().equals(endNode.getBuildFile())) {
LOG.error("Conflict on build file " + targetBuildFile + " vs "
+ endNode.getBuildFile());
conflict = true;
index++;
endNodeName = "[" + targetName + " (" + index + ")]";
}
} while (conflict);
}
// Creates an new node if none found.
if (endNode == null) {
LOG.info("Target " + startNode + " has dependency to non existent target "
+ endNodeName + ", creating a dummy node");
endNode = (AntTargetNode) graph.createNode(endNodeName);
endNode.setAttributes(Node.ATTR_MISSING_NODE);
}
if (!isSameBuildFile) {
endNode.setBuildFile(targetBuildFile.getAbsolutePath());
}
return endNode;
}
}