| /******************************************************************************* |
| * Copyright (c) 2005, 2017 IBM Corporation and others. |
| * 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.debug.core.model; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.model.IStackFrame; |
| import org.eclipse.debug.core.model.IThread; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.dbgp.IDbgpFeature; |
| import org.eclipse.dltk.dbgp.IDbgpSession; |
| import org.eclipse.dltk.dbgp.IDbgpStreamFilter; |
| import org.eclipse.dltk.dbgp.IDbgpStreamListener; |
| import org.eclipse.dltk.dbgp.breakpoints.IDbgpBreakpoint; |
| import org.eclipse.dltk.dbgp.breakpoints.IDbgpLineBreakpoint; |
| import org.eclipse.dltk.dbgp.commands.IDbgpFeatureCommands; |
| import org.eclipse.dltk.dbgp.exceptions.DbgpException; |
| import org.eclipse.dltk.debug.core.DLTKDebugPlugin; |
| import org.eclipse.dltk.debug.core.DebugOption; |
| import org.eclipse.dltk.debug.core.IDebugOptions; |
| import org.eclipse.dltk.debug.core.model.IScriptDebugThreadConfigurator; |
| import org.eclipse.dltk.debug.core.model.IScriptStackFrame; |
| import org.eclipse.dltk.debug.core.model.IScriptThread; |
| import org.eclipse.dltk.internal.debug.core.model.operations.DbgpDebugger; |
| |
| public class ScriptThreadManager |
| implements IScriptThreadManager, IDbgpStreamListener { |
| private static final boolean DEBUG = DLTKCore.DEBUG; |
| |
| private IScriptDebugThreadConfigurator configurator = null; |
| |
| // Helper methods |
| private interface IThreadBoolean { |
| boolean get(IThread thread); |
| } |
| |
| private boolean getThreadBoolean(IThreadBoolean b) { |
| synchronized (threads) { |
| IThread[] ths = getThreads(); |
| |
| if (ths.length == 0) { |
| return false; |
| } |
| |
| for (int i = 0; i < ths.length; ++i) { |
| if (!b.get(ths[i])) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| } |
| |
| private final ListenerList<IScriptThreadManagerListener> listeners = new ListenerList<>( |
| ListenerList.IDENTITY); |
| |
| private final List<IScriptThread> threads = new ArrayList<>(); |
| |
| private volatile boolean waitingForThreads = true; |
| |
| private final ScriptDebugTarget target; |
| |
| protected void fireThreadAccepted(IScriptThread thread, boolean first) { |
| for (IScriptThreadManagerListener listener : listeners) { |
| listener.threadAccepted(thread, first); |
| } |
| } |
| |
| protected void fireAllThreadsTerminated() { |
| for (IScriptThreadManagerListener listener : listeners) { |
| listener.allThreadsTerminated(); |
| } |
| } |
| |
| @Override |
| public void addListener(IScriptThreadManagerListener listener) { |
| listeners.add(listener); |
| } |
| |
| @Override |
| public void removeListener(IScriptThreadManagerListener listener) { |
| listeners.remove(listener); |
| } |
| |
| @Override |
| public boolean isWaitingForThreads() { |
| return waitingForThreads; |
| } |
| |
| @Override |
| public boolean hasThreads() { |
| synchronized (threads) { |
| return !threads.isEmpty(); |
| } |
| } |
| |
| @Override |
| public IScriptThread[] getThreads() { |
| synchronized (threads) { |
| return threads.toArray(new IScriptThread[threads.size()]); |
| } |
| } |
| |
| public ScriptThreadManager(ScriptDebugTarget target) { |
| if (target == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| this.target = target; |
| } |
| |
| private IDbgpStreamFilter[] streamFilters = null; |
| |
| /** |
| * @param data |
| * @param stdout |
| * @return |
| */ |
| private String filter(String data, int stream) { |
| if (streamFilters != null) { |
| for (int i = 0; i < streamFilters.length; ++i) { |
| data = streamFilters[i].filter(data, stream); |
| if (data == null) { |
| return null; |
| } |
| } |
| } |
| return data; |
| } |
| |
| @Override |
| public void stdoutReceived(String data) { |
| final IScriptStreamProxy proxy = target.getStreamProxy(); |
| if (proxy != null) { |
| data = filter(data, IDbgpStreamFilter.STDOUT); |
| if (data != null) { |
| proxy.writeStdout(data); |
| } |
| } |
| if (DEBUG) { |
| System.out.println("Received (stdout): " + data); //$NON-NLS-1$ |
| } |
| } |
| |
| @Override |
| public void stderrReceived(String data) { |
| final IScriptStreamProxy proxy = target.getStreamProxy(); |
| if (proxy != null) { |
| data = filter(data, IDbgpStreamFilter.STDERR); |
| if (data != null) { |
| proxy.writeStderr(data); |
| } |
| } |
| if (DEBUG) { |
| System.out.println("Received (stderr): " + data); //$NON-NLS-1$ |
| } |
| } |
| |
| void setStreamFilters(IDbgpStreamFilter[] streamFilters) { |
| this.streamFilters = streamFilters; |
| } |
| |
| /** |
| * Tests if the specified thread has breakpoint at the same line |
| * |
| * @param thread |
| * @return |
| */ |
| private static boolean hasBreakpointAtCurrentPosition(ScriptThread thread) { |
| try { |
| thread.updateStack(); |
| if (thread.hasStackFrames()) { |
| final IStackFrame top = thread.getTopStackFrame(); |
| if (top instanceof IScriptStackFrame |
| && top.getLineNumber() > 0) { |
| final IScriptStackFrame frame = (IScriptStackFrame) top; |
| if (frame.getSourceURI() != null) { |
| final String location = frame.getSourceURI().getPath(); |
| final IDbgpBreakpoint[] breakpoints = thread |
| .getDbgpSession().getCoreCommands() |
| .getBreakpoints(); |
| for (int i = 0; i < breakpoints.length; ++i) { |
| if (breakpoints[i] instanceof IDbgpLineBreakpoint) { |
| final IDbgpLineBreakpoint bp = (IDbgpLineBreakpoint) breakpoints[i]; |
| if (frame.getLineNumber() == bp |
| .getLineNumber()) { |
| try { |
| if (new URI(bp.getFilename()).getPath() |
| .equals(location)) { |
| return true; |
| } |
| } catch (URISyntaxException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } catch (DebugException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } catch (DbgpException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Tests if the specified thread has valid current stack. In some cases it |
| * is better to skip first internal location. |
| * |
| * @param thread |
| * @return |
| */ |
| private static boolean isValidStack(ScriptThread thread) { |
| final IDebugOptions debugOptions = thread.getDbgpSession() |
| .getDebugOptions(); |
| if (debugOptions.get(DebugOption.ENGINE_VALIDATE_STACK)) { |
| thread.updateStack(); |
| if (thread.hasStackFrames()) { |
| return thread.isValidStack(); |
| } |
| } |
| return true; |
| } |
| |
| // IDbgpThreadAcceptor |
| @Override |
| public void acceptDbgpThread(IDbgpSession session, |
| IProgressMonitor monitor) { |
| SubMonitor sub = SubMonitor.convert(monitor, 100); |
| try { |
| DbgpException error = session.getInfo().getError(); |
| if (error != null) { |
| throw error; |
| } |
| session.configure(target.getOptions()); |
| session.getStreamManager().addListener(this); |
| |
| final boolean breakOnFirstLine = target.breakOnFirstLineEnabled() |
| || isAnyThreadInStepInto(); |
| ScriptThread thread = new ScriptThread(target, session, this); |
| thread.initialize(sub.newChild(25)); |
| addThread(thread); |
| |
| final boolean isFirstThread = waitingForThreads; |
| if (isFirstThread) { |
| waitingForThreads = false; |
| } |
| if (isFirstThread || !isSupportsThreads(thread)) { |
| SubMonitor child = sub.newChild(25); |
| target.breakpointManager |
| .initializeSession(thread.getDbgpSession(), child); |
| child = sub.newChild(25); |
| if (configurator != null) { |
| configurator.initializeBreakpoints(thread, child); |
| } |
| } |
| |
| DebugEventHelper.fireCreateEvent(thread); |
| |
| final boolean stopBeforeCode = thread.getDbgpSession() |
| .getDebugOptions().get(DebugOption.ENGINE_STOP_BEFORE_CODE); |
| boolean executed = false; |
| if (!breakOnFirstLine) { |
| if (stopBeforeCode || !hasBreakpointAtCurrentPosition(thread)) { |
| thread.resume(); |
| executed = true; |
| } |
| } else { |
| if (stopBeforeCode || !isValidStack(thread)) { |
| thread.initialStepInto(); |
| executed = true; |
| } |
| } |
| if (!executed) { |
| if (!thread.isStackInitialized()) { |
| thread.updateStack(); |
| } |
| DebugEventHelper.fireChangeEvent(thread); |
| DebugEventHelper.fireSuspendEvent(thread, |
| DebugEvent.CLIENT_REQUEST); |
| } |
| sub.worked(25); |
| fireThreadAccepted(thread, isFirstThread); |
| } catch (Exception e) { |
| try { |
| target.terminate(); |
| } catch (DebugException e1) { |
| } |
| DLTKDebugPlugin.log(e); |
| } finally { |
| sub.done(); |
| } |
| } |
| |
| private static boolean isSupportsThreads(IScriptThread thread) { |
| try { |
| final IDbgpFeature feature = thread.getDbgpSession() |
| .getCoreCommands() |
| .getFeature(IDbgpFeatureCommands.LANGUAGE_SUPPORTS_THREADS); |
| return feature != null |
| && IDbgpFeature.ONE_VALUE.equals(feature.getValue()); |
| } catch (DbgpException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| return false; |
| } |
| } |
| |
| private boolean isAnyThreadInStepInto() { |
| synchronized (threads) { |
| for (Iterator<IScriptThread> i = threads.iterator(); i.hasNext();) { |
| ScriptThread thread = (ScriptThread) i.next(); |
| if (thread.isStepInto()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void addThread(ScriptThread thread) { |
| synchronized (threads) { |
| threads.add(thread); |
| } |
| } |
| |
| @Override |
| public void terminateThread(IScriptThread thread) { |
| synchronized (threads) { |
| threads.remove(thread); |
| } |
| DebugEventHelper.fireTerminateEvent(thread); |
| final IDbgpSession session = ((ScriptThread) thread).getDbgpSession(); |
| session.getStreamManager().removeListener(this); |
| target.breakpointManager.removeSession(thread.getDbgpSession()); |
| if (!hasThreads()) { |
| fireAllThreadsTerminated(); |
| } |
| } |
| |
| // ITerminate |
| @Override |
| public boolean canTerminate() { |
| synchronized (threads) { |
| IThread[] ths = getThreads(); |
| |
| if (ths.length == 0) { |
| if (waitingForThreads) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| for (int i = 0; i < ths.length; ++i) { |
| if (!ths[i].canTerminate()) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| } |
| |
| @Override |
| public boolean isTerminated() { |
| if (!hasThreads()) { |
| return !isWaitingForThreads(); |
| } |
| |
| return getThreadBoolean(thread -> thread.isTerminated()); |
| } |
| |
| @Override |
| public void terminate() throws DebugException { |
| target.terminate(); |
| } |
| |
| @Override |
| public void sendTerminationRequest() throws DebugException { |
| synchronized (threads) { |
| IScriptThread[] threads = getThreads(); |
| for (int i = 0; i < threads.length; ++i) { |
| threads[i].sendTerminationRequest(); |
| } |
| waitingForThreads = false; |
| } |
| } |
| |
| @Override |
| public boolean canResume() { |
| return getThreadBoolean(thread -> thread.canResume()); |
| } |
| |
| @Override |
| public boolean canSuspend() { |
| return getThreadBoolean(thread -> thread.canSuspend()); |
| } |
| |
| @Override |
| public boolean isSuspended() { |
| return getThreadBoolean(thread -> thread.isSuspended()); |
| } |
| |
| @Override |
| public void resume() throws DebugException { |
| synchronized (threads) { |
| IThread[] threads = getThreads(); |
| for (int i = 0; i < threads.length; ++i) { |
| threads[i].resume(); |
| } |
| } |
| } |
| |
| @Override |
| public void suspend() throws DebugException { |
| synchronized (threads) { |
| IThread[] threads = getThreads(); |
| for (int i = 0; i < threads.length; ++i) { |
| threads[i].suspend(); |
| } |
| } |
| } |
| |
| @Override |
| public void refreshThreads() { |
| synchronized (threads) { |
| IThread[] threads = getThreads(); |
| for (int i = 0; i < threads.length; ++i) { |
| ((IScriptThread) threads[i]).updateStackFrames(); |
| } |
| } |
| } |
| |
| @Override |
| public void setScriptThreadConfigurator( |
| IScriptDebugThreadConfigurator configurator) { |
| this.configurator = configurator; |
| } |
| |
| @Override |
| public void configureThread(DbgpDebugger engine, |
| ScriptThread scriptThread) { |
| if (configurator != null) { |
| configurator.configureThread(engine, scriptThread); |
| } |
| } |
| |
| } |