blob: 747f624038846387fb2122f4cfadc0c2066ac705 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ant.internal.launching.debug;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.MacroInstance;
import org.eclipse.ant.internal.launching.debug.model.DebugMessageIds;
public class AntDebugState {
private static final String fgAntTaskName = "ant"; //$NON-NLS-1$
private static final String fgAntCallTaskName = "antcall"; //$NON-NLS-1$
private IDebugBuildLogger fLogger;
@SuppressWarnings("unused")
private Stack<Task> fTasks = new Stack<>();
@SuppressWarnings("unused")
private Map<Task, Object> fTaskToProxies = new HashMap<>();
private Task fCurrentTask;
private Task fStepOverTask;
private Task fStepIntoTask;
private Task fLastTaskFinished;
// properties set before execution
private Map<String, Object> fInitialProperties = null;
private Map<String, Object> fProperties = null;
private Map<Project, Vector<?>> fProjectToTargetNames = null;
private Map<Project, Map<Target, Vector<Target>>> fProjectToMapOfTargetToBuildSequence = null;
@SuppressWarnings("unused")
private Stack<Target> fTargetsToExecute = new Stack<>();
@SuppressWarnings("unused")
private Stack<Target> fTargetsExecuting = new Stack<>();
private boolean fConsiderTargetBreakpoints = false;
private boolean fShouldSuspend;
private boolean fClientSuspend = false;
private boolean fStepIntoSuspend = false;
private boolean fIsAfterTaskEvent = false;
public AntDebugState(IDebugBuildLogger logger) {
fLogger = logger;
}
@SuppressWarnings("unused")
public void buildStarted() {
fProjectToTargetNames = new HashMap<>();
fProjectToMapOfTargetToBuildSequence = new HashMap<>();
}
/**
* Call-back from {@link org.eclipse.ant.internal.launching.runtime.logger.AntProcessDebugBuildLogger} when the build has finished
*
* @since 1.0.1
*/
public void buildFinished() {
if (fProjectToTargetNames != null) {
fProjectToTargetNames.clear();
}
if (fProjectToMapOfTargetToBuildSequence != null) {
fProjectToMapOfTargetToBuildSequence.clear();
}
fTargetsExecuting.clear();
fTargetsToExecute.clear();
if (fInitialProperties != null) {
fInitialProperties.clear();
}
if (fProperties != null) {
fProperties.clear();
}
if (fTaskToProxies != null) {
fTaskToProxies.clear();
}
if (fTasks != null) {
fTasks.clear();
}
}
public void waitIfSuspended() {
fLogger.waitIfSuspended();
}
public Task getLastTaskFinished() {
return fLastTaskFinished;
}
private void setLastTaskFinished(Task lastTaskFinished) {
fLastTaskFinished = lastTaskFinished;
}
public Task getCurrentTask() {
return fCurrentTask;
}
public void setCurrentTask(Task currentTask) {
fCurrentTask = currentTask;
}
private Map<String, Object> getInitialProperties() {
return fInitialProperties;
}
public Task getStepOverTask() {
return fStepOverTask;
}
public void setStepOverTask(Task stepOverTask) {
fStepOverTask = stepOverTask;
}
private boolean considerTargetBreakpoints() {
return fConsiderTargetBreakpoints;
}
private void setConsiderTargetBreakpoints(boolean considerTargetBreakpoints) {
fConsiderTargetBreakpoints = considerTargetBreakpoints;
}
private Stack<Task> getTasks() {
return fTasks;
}
public void setShouldSuspend(boolean shouldSuspend) {
fShouldSuspend = shouldSuspend;
}
public boolean shouldSuspend() {
return fShouldSuspend;
}
private Map<Target, Vector<Target>> getTargetToBuildSequence(Project project) {
return fProjectToMapOfTargetToBuildSequence.get(project);
}
public void setTargetToExecute(Target target) {
if (target == null) {
if (!fTargetsToExecute.isEmpty()) {
fTargetsToExecute.pop();
}
} else {
fTargetsToExecute.push(target);
}
}
public void setTargetExecuting(Target target) {
if (target == null) {
if (!fTargetsExecuting.isEmpty()) {
fTargetsExecuting.pop();
}
} else {
fTargetsExecuting.push(target);
}
}
private Target getTargetToExecute() {
if (fTargetsToExecute.isEmpty()) {
return null;
}
return fTargetsToExecute.peek();
}
private Target getTargetExecuting() {
if (fTargetsExecuting.isEmpty()) {
return null;
}
return fTargetsExecuting.peek();
}
public boolean isStepIntoSuspend() {
return isAfterTaskEvent() && fStepIntoSuspend;
}
public void setStepIntoSuspend(boolean stepIntoSuspend) {
fStepIntoSuspend = stepIntoSuspend;
}
public boolean isClientSuspend() {
return fClientSuspend;
}
public void setClientSuspend(boolean clientSuspend) {
fClientSuspend = clientSuspend;
}
public Task getStepIntoTask() {
return fStepIntoTask;
}
public void setStepIntoTask(Task stepIntoTask) {
fStepIntoTask = stepIntoTask;
}
public void resume() {
fLogger.notifyAll();
}
public Map<String, Object> getProperties() {
return fProperties;
}
public Location getBreakpointLocation() {
if (isAfterTaskEvent() && getCurrentTask() != null) {
return getCurrentTask().getLocation();
}
if (considerTargetBreakpoints()) {
Target targetExecuting = getTargetExecuting();
if (targetExecuting != null) {
return getLocation(targetExecuting);
}
}
return null;
}
private boolean isAfterTaskEvent() {
return fIsAfterTaskEvent;
}
private void setAfterTaskEvent(boolean isAfterTaskEvent) {
fIsAfterTaskEvent = isAfterTaskEvent;
}
public void taskStarted(BuildEvent event) {
setAfterTaskEvent(true);
if (getInitialProperties() == null) {// implicit or top level target does not fire targetStarted()
fInitialProperties = event.getProject().getProperties();
}
setCurrentTask(event.getTask());
setConsiderTargetBreakpoints(false);
Stack<Task> tasks = getTasks();
if (!tasks.isEmpty()) {
// cache the parent task proxy as when that task is started or finished the
// proxy is not yet available or is nulled out
Task parentTask = tasks.peek();
Object proxy = parentTask.getRuntimeConfigurableWrapper().getProxy();
if (proxy != null) {
fTaskToProxies.put(parentTask, proxy);
}
}
tasks.push(getCurrentTask());
waitIfSuspended();
}
public void taskFinished() {
Stack<Task> tasks = getTasks();
if (!tasks.empty()) {
Task lastTask = tasks.pop();
setLastTaskFinished(lastTask);
setCurrentTask(null);
String taskName = lastTask.getTaskName();
if (getStepOverTask() != null) {
if ((fgAntCallTaskName.equals(taskName) || fgAntTaskName.equals(taskName))
&& (!fgAntCallTaskName.equals(getStepOverTask().getTaskName()) && !fgAntTaskName.equals(getStepOverTask().getTaskName()))) {
setShouldSuspend(true);
} else if (fTaskToProxies.remove(lastTask) instanceof MacroInstance) {
setShouldSuspend(true);
}
}
}
waitIfSuspended();
}
public void stepOver() {
setStepOverTask(getCurrentTask());
if (getCurrentTask() == null) {
// stepping over target breakpoint
setShouldSuspend(true);
}
resume();
}
public void targetStarted(BuildEvent event) {
setAfterTaskEvent(false);
Project eventProject = event.getProject();
if (getInitialProperties() == null) {
fInitialProperties = eventProject.getProperties();
}
if (fProjectToTargetNames.get(eventProject) == null) {
Object ref = eventProject.getReference(IAntCoreConstants.TARGET_VECTOR_NAME);
if (ref != null) {
fProjectToTargetNames.put(eventProject, (Vector<?>) ref);
@SuppressWarnings("unused")
HashMap<Target, Vector<Target>> targetToBuildSequence = new HashMap<>();
setTargetToExecute(initializeBuildSequenceInformation(event, targetToBuildSequence));
fProjectToMapOfTargetToBuildSequence.put(eventProject, targetToBuildSequence);
}
}
setTargetExecuting(event.getTarget());
if (event.getTarget().equals(getTargetToExecute())) {
// the dependencies of the target to execute have been met
// prepare for the next target
Vector<?> targets = fProjectToTargetNames.get(eventProject);
if (!targets.isEmpty()) {
setTargetToExecute(eventProject.getTargets().get(targets.remove(0)));
} else {
setTargetToExecute(null);
}
}
setConsiderTargetBreakpoints(true);
}
public int getLineNumber(Location location) {
try { // succeeds with Ant newer than 1.6
return location.getLineNumber();
}
catch (NoSuchMethodError e) {
// Ant before 1.6
String locationString = location.toString();
if (locationString.length() == 0) {
return 0;
}
// filename: lineNumber: ("c:\buildfile.xml: 12: ")
int lastIndex = locationString.lastIndexOf(':');
int index = locationString.lastIndexOf(':', lastIndex - 1);
if (index != -1) {
try {
return Integer.parseInt(locationString.substring(index + 1, lastIndex));
}
catch (NumberFormatException nfe) {
return 0;
}
}
return 0;
}
}
public static Location getLocation(Target target) {
try {// succeeds with Ant newer than 1.6.2
return target.getLocation();
}
catch (NoSuchMethodError e) {
return Location.UNKNOWN_LOCATION;
}
}
public String getFileName(Location location) {
try {// succeeds with Ant newer than 1.6
return location.getFileName();
}
catch (NoSuchMethodError e) {
// Ant before 1.6
String locationString = location.toString();
if (locationString.length() == 0) {
return null;
}
// filename: lineNumber: ("c:\buildfile.xml: 12: ")
int lastIndex = locationString.lastIndexOf(':');
int index = locationString.lastIndexOf(':', lastIndex - 1);
if (index == -1) {
index = lastIndex; // only the filename is known
}
if (index != -1) {
// bug 84403
// if (locationString.startsWith("file:")) {
// return FileUtils.newFileUtils().fromURI(locationString);
// }
// remove file:
return locationString.substring(5, index);
}
return null;
}
}
private void appendToStack(StringBuffer stackRepresentation, String targetName, String taskName, Location location) {
stackRepresentation.append(targetName);
stackRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
stackRepresentation.append(taskName);
stackRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
stackRepresentation.append(getFileName(location));
stackRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
stackRepresentation.append(getLineNumber(location));
stackRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
}
public void marshalStack(StringBuffer stackRepresentation) {
Stack<Task> tasks = getTasks();
stackRepresentation.append(DebugMessageIds.STACK);
stackRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
Target targetToExecute = getTargetToExecute();
Target targetExecuting = getTargetExecuting();
Project projectExecuting = null;
if (targetExecuting != null) {
projectExecuting = targetExecuting.getProject();
} else if (!tasks.empty()) { // no target...must be a task
Task task = tasks.peek();
projectExecuting = task.getProject();
}
if (!isAfterTaskEvent()) {
appendToStack(stackRepresentation, targetExecuting.getName(), IAntCoreConstants.EMPTY_STRING, getLocation(targetExecuting));
}
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
if (task.getProject() == projectExecuting) {
appendToStack(stackRepresentation, task.getOwningTarget().getName(), task.getTaskName(), task.getLocation());
} else {
// sub build target dependencies
String targetName = task.getOwningTarget().getName();
if (targetName != null && targetName.length() != 0) { // skip for implicit target
Iterator<Target> itr = fTargetsToExecute.iterator();
while (itr.hasNext()) {
Target target = itr.next();
if (target.getProject() != projectExecuting) {
targetToExecute = target;
continue;
}
marshalTargetDependancyStack(stackRepresentation, target, targetExecuting);
}
}
projectExecuting = task.getProject();
targetExecuting = task.getOwningTarget();
appendToStack(stackRepresentation, targetExecuting.getName(), task.getTaskName(), task.getLocation());
}
}
// target dependency stack
marshalTargetDependancyStack(stackRepresentation, targetToExecute, targetExecuting);
}
private void marshalTargetDependancyStack(StringBuffer stackRepresentation, Target targetToExecute, Target targetExecuting) {
if (targetToExecute != null) {
Vector<Target> buildSequence = getTargetToBuildSequence(targetToExecute.getProject()).get(targetToExecute);
int startIndex = buildSequence.indexOf(targetExecuting) + 1;
int dependancyStackDepth = buildSequence.indexOf(targetToExecute);
Target stackTarget;
for (int i = startIndex; i <= dependancyStackDepth; i++) {
stackTarget = buildSequence.get(i);
if (stackTarget.dependsOn(targetExecuting.getName())) {
appendToStack(stackRepresentation, stackTarget.getName(), IAntCoreConstants.EMPTY_STRING, getLocation(stackTarget));
}
}
}
}
public void marshallProperties(StringBuffer propertiesRepresentation, boolean escapeLineSep) {
Stack<Task> tasks = getTasks();
if (tasks.isEmpty()) {
return;
}
propertiesRepresentation.append(DebugMessageIds.PROPERTIES);
propertiesRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
Project project = tasks.peek().getProject();
Map<String, Object> lastProperties = getProperties();
Map<String, Object> currentProperties = project.getProperties();
if (lastProperties != null && currentProperties.size() == lastProperties.size()) {
// no new properties
return;
}
Map<String, Object> initialProperties = getInitialProperties();
Map<String, Object> currentUserProperties = project.getUserProperties();
String originalPropertyName;
String propertyValue;
for (String propertyName : currentProperties.keySet()) {
originalPropertyName = propertyName;
if (lastProperties == null || lastProperties.get(propertyName) == null) { // new property
if (escapeLineSep) {
propertyName = escapeLineSeparator(propertyName);
}
propertiesRepresentation.append(propertyName.length());
propertiesRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
propertiesRepresentation.append(propertyName);
propertiesRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
propertyValue = (String) currentProperties.get(originalPropertyName);
if (escapeLineSep) {
propertyValue = escapeLineSeparator(propertyValue);
}
propertiesRepresentation.append(propertyValue.length());
propertiesRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
propertiesRepresentation.append(propertyValue);
propertiesRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
propertiesRepresentation.append(getPropertyType(initialProperties, currentUserProperties, originalPropertyName));
propertiesRepresentation.append(DebugMessageIds.MESSAGE_DELIMITER);
}
}
propertiesRepresentation.deleteCharAt(propertiesRepresentation.length() - 1);
fProperties = currentProperties;
}
private int getPropertyType(Map<String, Object> initialProperties, Map<String, Object> currentUserProperties, String propertyName) {
if (initialProperties.get(propertyName) != null) { // properties set before the start of the build
if (currentUserProperties.get(propertyName) == null) {
return DebugMessageIds.PROPERTY_SYSTEM;
}
return DebugMessageIds.PROPERTY_USER;
} else if (currentUserProperties.get(propertyName) == null) {
return DebugMessageIds.PROPERTY_RUNTIME;
} else {
return DebugMessageIds.PROPERTY_USER;
}
}
private String escapeLineSeparator(String stringToEscape) {
if (!(stringToEscape.indexOf('\r') != -1 || stringToEscape.indexOf('\n') != -1 || stringToEscape.contains("\\r") //$NON-NLS-1$
|| stringToEscape.contains("\\n"))) { //$NON-NLS-1$
return stringToEscape;
}
StringBuilder escapedValue = new StringBuilder(stringToEscape);
for (int i = 0; i < escapedValue.length(); i++) {
switch (escapedValue.charAt(i)) {
case '\r':
escapedValue.replace(i, i + 1, "\\r"); //$NON-NLS-1$
i++;
break;
case '\n':
escapedValue.replace(i, i + 1, "\\n"); //$NON-NLS-1$
i++;
break;
case '\\':
if (escapedValue.charAt(i + 1) == 'r' || escapedValue.charAt(i + 1) == 'n') {
escapedValue.replace(i, i + 1, "\\\\"); //$NON-NLS-1$
i++;
}
break;
default:
break;
}
}
return escapedValue.toString();
}
private Target initializeBuildSequenceInformation(BuildEvent event, Map<Target, Vector<Target>> targetToBuildSequence) {
Project antProject = event.getProject();
Vector<String> targets = antProject.getReference(IAntCoreConstants.TARGET_VECTOR_NAME);
if (targets == null || targets.size() < 1) {
return null;
}
Hashtable<String, Target> allTargets = antProject.getTargets();
Vector<Target> sortedTargets;
for (String targetName : targets) {
sortedTargets = antProject.topoSort(targetName, allTargets);
targetToBuildSequence.put(allTargets.get(targetName), sortedTargets);
}
// the target to execute
return allTargets.get(targets.remove(0));
}
}