/*
* #%~
* org.overture.ide.debug
* %%
* Copyright (C) 2008 - 2014 Overture
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #~%
*/
package org.overture.ide.debug.core.model.internal;
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.overture.ide.debug.core.DebugOption;
import org.overture.ide.debug.core.IDebugOptions;
import org.overture.ide.debug.core.VdmDebugPlugin;
import org.overture.ide.debug.core.dbgp.IDbgpFeature;
import org.overture.ide.debug.core.dbgp.IDbgpSession;
import org.overture.ide.debug.core.dbgp.IDbgpStreamFilter;
import org.overture.ide.debug.core.dbgp.IDbgpStreamListener;
import org.overture.ide.debug.core.dbgp.breakpoints.IDbgpBreakpoint;
import org.overture.ide.debug.core.dbgp.breakpoints.IDbgpLineBreakpoint;
import org.overture.ide.debug.core.dbgp.commands.IDbgpFeatureCommands;
import org.overture.ide.debug.core.dbgp.exceptions.DbgpException;
import org.overture.ide.debug.core.model.DebugEventHelper;
import org.overture.ide.debug.core.model.IVdmDebugThreadConfigurator;
import org.overture.ide.debug.core.model.IVdmStackFrame;
import org.overture.ide.debug.core.model.IVdmThread;
import org.overture.ide.debug.core.model.internal.operations.DbgpDebugger;
public class VdmThreadManager implements IVdmThreadManager, IDbgpStreamListener
{
private static final boolean DEBUG = VdmDebugPlugin.DEBUG;
private boolean errorState = false;
private IVdmDebugThreadConfigurator 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 listeners = new ListenerList(ListenerList.IDENTITY);
private final List<VdmThread> threads = new ArrayList<VdmThread>();
private volatile boolean waitingForThreads = true;
private final VdmDebugTarget target;
protected void fireThreadAccepted(IVdmThread thread, boolean first)
{
Object[] list = listeners.getListeners();
for (int i = 0; i < list.length; ++i)
{
((IVdmThreadManagerListener) list[i]).threadAccepted(thread, first);
}
}
protected void fireAllThreadsTerminated()
{
Object[] list = listeners.getListeners();
for (int i = 0; i < list.length; ++i)
{
((IVdmThreadManagerListener) list[i]).allThreadsTerminated();
}
}
public void addListener(IVdmThreadManagerListener listener)
{
listeners.add(listener);
}
public void removeListener(IVdmThreadManagerListener listener)
{
listeners.remove(listener);
}
public boolean isWaitingForThreads()
{
return waitingForThreads;
}
public boolean hasThreads()
{
synchronized (threads)
{
return !threads.isEmpty();
}
}
public IVdmThread[] getThreads()
{
synchronized (threads)
{
return (IVdmThread[]) threads.toArray(new IVdmThread[threads.size()]);
}
}
public VdmThreadManager(VdmDebugTarget 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;
}
public void stdoutReceived(String data)
{
final IVdmStreamProxy 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$
}
}
public void stderrReceived(String data)
{
final IVdmStreamProxy 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$
}
DebugEventHelper.fireChangeEvent(target);
}
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(VdmThread thread)
{
try
{
thread.updateStack();
if (thread.hasStackFrames())
{
final IStackFrame top = thread.getTopStackFrame();
if (top instanceof IVdmStackFrame && top.getLineNumber() > 0)
{
final IVdmStackFrame frame = (IVdmStackFrame) 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 (VdmDebugPlugin.DEBUG)
{
e.printStackTrace();
}
}
}
}
}
}
}
}
} catch (DebugException e)
{
if (VdmDebugPlugin.DEBUG)
{
e.printStackTrace();
}
} catch (DbgpException e)
{
if (VdmDebugPlugin.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(VdmThread 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
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();
VdmThread thread = new VdmThread(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.resumeAfterAccept();
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)
{
}
VdmDebugPlugin.log(e);
} finally
{
sub.done();
}
}
private static boolean isSupportsThreads(IVdmThread 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 (VdmDebugPlugin.DEBUG)
{
e.printStackTrace();
}
return false;
}
}
private boolean isAnyThreadInStepInto()
{
synchronized (threads)
{
for (Iterator<VdmThread> i = threads.iterator(); i.hasNext();)
{
VdmThread thread = (VdmThread) i.next();
if (thread.isStepInto())
{
return true;
}
}
}
return false;
}
private void addThread(VdmThread thread)
{
synchronized (threads)
{
threads.add(thread);
}
}
public void terminateThread(IVdmThread thread)
{
synchronized (threads)
{
threads.remove(thread);
}
DebugEventHelper.fireTerminateEvent(thread);
final IDbgpSession session = ((VdmThread) thread).getDbgpSession();
session.getStreamManager().removeListener(this);
target.breakpointManager.removeSession(thread.getDbgpSession());
if (!hasThreads())
{
fireAllThreadsTerminated();
}
}
// ITerminate
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;
}
}
public boolean isTerminated()
{
if (!hasThreads())
{
return !isWaitingForThreads();
}
return getThreadBoolean(new IThreadBoolean()
{
public boolean get(IThread thread)
{
return thread.isTerminated();
}
});
}
public void terminate() throws DebugException
{
target.terminate();
}
public void sendTerminationRequest() throws DebugException
{
synchronized (threads)
{
IVdmThread[] threads = getThreads();
if (threads.length > 0)
{
waitingForThreads = false;
handleCustomPreTerminationCommands();
}
for (int i = 0; i < threads.length; ++i)
{
threads[i].sendTerminationRequest();
}
}
}
public boolean canResume()
{
if (errorState)
{
return false;
}
return getThreadBoolean(new IThreadBoolean()
{
public boolean get(IThread thread)
{
return thread.canResume();
}
});
}
public boolean canSuspend()
{
return getThreadBoolean(new IThreadBoolean()
{
public boolean get(IThread thread)
{
return thread.canSuspend();
}
});
}
public boolean isSuspended()
{
return getThreadBoolean(new IThreadBoolean()
{
public boolean get(IThread thread)
{
return thread.isSuspended();
}
});
}
public void resume() throws DebugException
{
synchronized (threads)
{
IThread[] threads = getThreads();
for (int i = 0; i < threads.length; ++i)
{
((VdmThread) threads[i]).resumeInner();
}
}
}
public void stepInto() throws DebugException
{
synchronized (threads)
{
IThread[] threads = getThreads();
// System.out.println("Thread number:" + threads.length );
for (int i = 0; i < threads.length; ++i)
{
((VdmThread) threads[i]).stepIntoInner();
// System.out.println("Step Thread: " + i);
}
}
}
public void stepOver() throws DebugException
{
synchronized (threads)
{
IThread[] threads = getThreads();
for (int i = 0; i < threads.length; ++i)
{
((VdmThread) threads[i]).stepOverInner();
}
}
}
public void stepReturn() throws DebugException
{
synchronized (threads)
{
IThread[] threads = getThreads();
for (int i = 0; i < threads.length; ++i)
{
((VdmThread) threads[i]).stepReturnInner();
}
}
}
public void suspend() throws DebugException
{
synchronized (threads)
{
IThread[] threads = getThreads();
for (int i = 0; i < threads.length; ++i)
{
threads[i].suspend();
}
}
}
public void refreshThreads()
{
synchronized (threads)
{
IThread[] threads = getThreads();
for (int i = 0; i < threads.length; ++i)
{
((IVdmThread) threads[i]).updateStackFrames();
}
}
}
public void setVdmThreadConfigurator(
IVdmDebugThreadConfigurator configurator)
{
this.configurator = configurator;
}
public void configureThread(DbgpDebugger engine, VdmThread scriptThread)
{
if (configurator != null)
{
configurator.configureThread(engine, scriptThread);
}
}
public void handleCustomTerminationCommands()
{
synchronized (threads)
{
if (threads.size() == 1)
{
target.handleCustomTerminationCommands(threads.get(0).getDbgpSession());
}
}
}
public void handleCustomPreTerminationCommands()
{
synchronized (threads)
{
if (!threads.isEmpty())
{
target.handleCustomTerminationCommands(threads.get(0).getDbgpSession());
}
}
}
}