| /******************************************************************************* |
| * Copyright (c) 2007 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 |
| *******************************************************************************/ |
| 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 breakpoints = Collections.synchronizedSet(new HashSet()); |
| private final Stack stack = new Stack(); |
| private StyleFrame stepOverFrame; |
| private BreakPoint breakpoint; |
| private URL sourceURL; |
| private Result result; |
| |
| private int stepOverStackSize; |
| |
| public void setInvoker(IProcessorInvoker invoker) |
| { |
| this.invoker = invoker; |
| } |
| |
| public void setEventWriter(Writer writer) |
| { |
| eventWriter = writer; |
| } |
| |
| public void setGeneratedWriter(Writer writer) |
| { |
| this.generatedWriter = writer; |
| } |
| |
| public void setSource(URL sourceURL) |
| { |
| this.sourceURL = sourceURL; |
| } |
| |
| public 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 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 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; |
| } |
| } |
| } |