blob: 3de1e07503b6f1ec17e796d9bc4b561179986be1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2010 Chase Technology Ltd - http://www.chasetechnology.co.uk
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Doug Satchwell (Chase Technology Ltd) - initial API and implementation
* David Carver (Intalio) - cleanup findbug errors.
*******************************************************************************/
package org.eclipse.wst.xsl.jaxp.debug.debugger;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.wst.xsl.jaxp.debug.invoker.IProcessorInvoker;
import org.eclipse.wst.xsl.jaxp.debug.invoker.TransformationException;
/**
* An implementation of <code>IXSLDebugger</code>.
*
* This class can be subclassed in order to provide debugging for a particular
* XSLT processor.
*
* @author Doug Satchwell
*/
public abstract class AbstractDebugger implements IXSLDebugger {
private static final Log log = LogFactory.getLog(AbstractDebugger.class);
private static final int ACTION_DO_NOTHING = 0;
private static final int ACTION_STOP = 1;
private static final int ACTION_QUIT = 2;
private static final int ACTION_SUSPEND = 3;
private static final int ACTION_RESUME = 4;
private static final int ACTION_STEP_INTO = 5;
private static final int ACTION_STEP_OVER = 6;
private static final int ACTION_STEP_RETURN = 7;
private static final String EVENT_STARTED = "started"; //$NON-NLS-1$
private static final String EVENT_STOPPED = "stopped"; //$NON-NLS-1$
private static final String EVENT_SUSPENDED = "suspended client"; //$NON-NLS-1$
private static final String EVENT_SUSPENDED_STEP = "suspended step"; //$NON-NLS-1$
private static final String EVENT_RESUMED = "resumed client"; //$NON-NLS-1$
private static final String EVENT_RESUMED_STEP = "resumed step"; //$NON-NLS-1$
private IProcessorInvoker invoker;
private int action;
private Writer eventWriter;
private Writer generatedWriter;
private final Set<BreakPoint> breakpoints = Collections
.synchronizedSet(new HashSet<BreakPoint>());
private final Stack<StyleFrame> stack = new Stack<StyleFrame>();
private StyleFrame stepOverFrame;
private BreakPoint breakpoint;
private URL sourceURL;
private Result result;
private int stepOverStackSize;
public synchronized void setInvoker(IProcessorInvoker invoker) {
this.invoker = invoker;
}
public synchronized void setEventWriter(Writer writer) {
eventWriter = writer;
}
public void setGeneratedWriter(Writer writer) {
this.generatedWriter = writer;
}
public synchronized void setSource(URL sourceURL) {
this.sourceURL = sourceURL;
}
public synchronized void setTarget(final Writer writer) {
result = new StreamResult(new Writer() {
public void write(char[] cbuf, int off, int len) throws IOException {
writer.write(cbuf, off, len);
generatedWriter.write(cbuf, off, len);
}
public void close() throws IOException {
writer.close();
generatedWriter.close();
}
public void flush() throws IOException {
writer.flush();
generatedWriter.flush();
}
});
}
public synchronized void run() {
if (action != ACTION_QUIT) {
debuggerStarted();
try {
invoker.transform(sourceURL, result);
} catch (TransformationException e) {
log.error("Transform failed", e); //$NON-NLS-1$
}
debuggerStopped();
}
}
public synchronized void suspend() {
action = ACTION_SUSPEND;
notify();
}
public synchronized void resume() {
action = ACTION_RESUME;
notify();
}
public synchronized void stepInto() {
action = ACTION_STEP_INTO;
notify();
}
public synchronized void stepOver() {
action = ACTION_STEP_OVER;
stepOverFrame = peekStyleFrame();
stepOverStackSize = stack.size();
notify();
}
public synchronized void stepReturn() {
action = ACTION_STEP_RETURN;
stepOverStackSize = stack.size();
notify();
}
public synchronized void quit() {
action = ACTION_QUIT;
}
public String stack() {
StringBuffer sb = new StringBuffer();
synchronized (stack) {
for (Iterator<StyleFrame> iter = stack.iterator(); iter.hasNext();) {
StyleFrame frame = (StyleFrame) iter.next();
sb.append(frame.toString());
for (Iterator<?> iter2 = frame.getVariableStack().iterator(); iter2
.hasNext();) {
sb.append("|"); //$NON-NLS-1$
Variable v = (Variable) iter2.next();
sb.append(v.getId());
}
if (iter.hasNext())
sb.append("$$$"); //$NON-NLS-1$
}
}
return sb.toString();
}
/**
* Check whether the debugger has been stopped and perform the appropriate
* action if so.
*/
public synchronized void checkStopped() {
if (action == ACTION_QUIT)
debuggerQuit();
else if (action == ACTION_STOP)
debuggerStopped();
}
/**
* Check whether the debugger is currently suspended or stepping at the
* given breakpoint and style frame, and perform the appropriate action if
* so.
*
* @param styleFrame
* the styleframe to check
* @param breakpoint
* the current location
*/
public synchronized void checkSuspended(StyleFrame styleFrame,
BreakPoint breakpoint) {
// do not suspend unless the line actually changed
if (breakpoint.equals(this.breakpoint))
return;
int stackSize;
synchronized (stack) {
stackSize = stack.size();
}
// do not suspend if there is nothing in the stack
if (stackSize == 0)
return;
switch (action) {
case ACTION_SUSPEND:
debuggerSuspendedClient(breakpoint);
break;
case ACTION_STEP_OVER:
// suspend if we are in the same template or we are moving up the
// stack
if (styleFrame.equals(stepOverFrame)
|| stackSize < stepOverStackSize)
debuggerSuspendedStep(breakpoint);
break;
case ACTION_STEP_INTO:
debuggerSuspendedStep(breakpoint);
break;
case ACTION_STEP_RETURN:
// suspend if we moved up the stack
if (stackSize < stepOverStackSize)
debuggerSuspendedStep(breakpoint);
break;
default:
checkBreakpoint(breakpoint);
}
}
private synchronized void checkBreakpoint(BreakPoint breakpoint) {
if (isBreakpoint(breakpoint))
debuggerSuspendedBreakpoint(breakpoint);
}
/**
* Called when the next transform in the pipeline has begun.
*/
public synchronized void debuggerTransformStarted() {
stack.clear();
}
protected synchronized void debuggerStarted() {
action = ACTION_DO_NOTHING;
sendEvent(EVENT_STARTED);
}
protected synchronized void debuggerStopped() {
action = ACTION_DO_NOTHING;
sendEvent(EVENT_STOPPED);
}
private synchronized void debuggerQuit() {
// just wait here indefinitely until the JVM exists, just to make sure
// we don't send any further events
try {
wait();
} catch (InterruptedException e) {
}
}
private synchronized void debuggerSuspendedBreakpoint(BreakPoint breakpoint) {
sendEvent("suspended breakpoint " + breakpoint); //$NON-NLS-1$
debuggerSuspended(breakpoint);
}
private synchronized void debuggerSuspendedStep(BreakPoint breakpoint) {
sendEvent(EVENT_SUSPENDED_STEP);
debuggerSuspended(breakpoint);
}
private synchronized void debuggerSuspendedClient(BreakPoint breakpoint) {
sendEvent(EVENT_SUSPENDED);
debuggerSuspended(breakpoint);
}
public synchronized void debuggerSuspended(BreakPoint breakpoint) {
this.breakpoint = breakpoint;
do {
try {
wait();
} catch (InterruptedException e) {
}
} while (action != ACTION_RESUME && action != ACTION_STEP_INTO
&& action != ACTION_STEP_OVER && action != ACTION_STEP_RETURN
&& action != ACTION_STOP);
debuggerResumed();
}
private synchronized void debuggerResumed() {
if (action == ACTION_STEP_INTO || action == ACTION_STEP_OVER
|| action == ACTION_STEP_RETURN)
sendEvent(EVENT_RESUMED_STEP);
else
sendEvent(EVENT_RESUMED);
}
private synchronized void sendEvent(String event) {
try {
log.info("Sending event: " + event + " eventWriter=" + eventWriter); //$NON-NLS-1$//$NON-NLS-2$
eventWriter.write(event + "\n"); //$NON-NLS-1$
eventWriter.flush();
} catch (IOException e) {
log.error("Error sending event", e); //$NON-NLS-1$
}
}
public void addBreakpoint(BreakPoint breakpoint) {
log.info("Adding breakpoint: " + breakpoint); //$NON-NLS-1$
breakpoints.add(breakpoint);
}
public void removeBreakpoint(BreakPoint breakpoint) {
log.info("Removing breakpoint: " + breakpoint); //$NON-NLS-1$
breakpoints.remove(breakpoint);
}
private boolean isBreakpoint(BreakPoint breakpoint) {
// do not check for breakpoint unless the line or filename actually
// changed
if (breakpoint.equals(this.breakpoint))
return false;
this.breakpoint = null;
return breakpoints.contains(breakpoint);
}
/**
* Pop a style frame from the stack.
*
* @return the popped style frame
*/
public StyleFrame popStyleFrame() {
synchronized (stack) {
StyleFrame styleFrame = (StyleFrame) stack.pop();
if (styleFrame.getParent() != null)
styleFrame.getParent().removeChild(styleFrame);
log
.trace("Popped frame: " + styleFrame + " (size after pop=" + stack.size() + ")"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
return styleFrame;
}
}
/**
* Push a style frame onto the stack.
*
* @param styleFrame
*/
public void pushStyleFrame(StyleFrame styleFrame) {
synchronized (stack) {
stack.push(styleFrame);
log
.trace("Pushed frame: " + styleFrame + " (size after push=" + stack.size() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
/**
* Peek a style frame from the stack.
*
* @return the peeked style frame
*/
public StyleFrame peekStyleFrame() {
synchronized (stack) {
if (stack.size() > 0)
return (StyleFrame) stack.peek();
return null;
}
}
}