/*******************************************************************************
* Copyright (c) 2007, 2016 Wind River Systems, Inc. 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
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.internal.debug.model;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.Launch;
import org.eclipse.tcf.internal.debug.Activator;
import org.eclipse.tcf.internal.debug.actions.TCFAction;
import org.eclipse.tcf.internal.debug.launch.TCFLaunchDelegate;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IPeer;
import org.eclipse.tcf.protocol.IService;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.JSON;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IContextQuery;
import org.eclipse.tcf.services.IDPrintf;
import org.eclipse.tcf.services.IFileSystem;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.services.IFileSystem.IFileHandle;
import org.eclipse.tcf.services.IMemory;
import org.eclipse.tcf.services.IMemoryMap;
import org.eclipse.tcf.services.IPathMap;
import org.eclipse.tcf.services.IProcesses;
import org.eclipse.tcf.services.IProcesses.ProcessContext;
import org.eclipse.tcf.services.IProcessesV1;
import org.eclipse.tcf.services.IRunControl;
import org.eclipse.tcf.services.IRunControl.RunControlContext;
import org.eclipse.tcf.services.IStreams;
import org.eclipse.tcf.util.TCFDataCache;
import org.eclipse.tcf.util.TCFTask;
/**
* TCFLaunch class represents an active TCF debug connection.
* The class handles initialization and synchronization of Memory Map and Path Map services,
* supports downloading and starting a remote process, maintains breakpoint status information, etc.
*/
public class TCFLaunch extends Launch {
/**
* Clients can use LaunchListener interface for notifications of launch being created, connected or
* disconnected. The interface also allows to receive remote process output.
*/
public interface LaunchListener {
public void onCreated(TCFLaunch launch);
public void onConnected(TCFLaunch launch);
public void onDisconnected(TCFLaunch launch);
public void onProcessOutput(TCFLaunch launch, String process_id, int stream_id, byte[] data);
public void onProcessStreamError(
TCFLaunch launch, String process_id, int stream_id,
Exception error, int lost_size);
}
/**
* Launch object handles queue of user actions, like debugger stepping commands.
* ActionsListener allows clients to be notified when an action execution is started and finished.
*/
public interface ActionsListener {
public void onContextActionStart(TCFAction action);
public void onContextActionResult(String id, String result);
public void onContextActionDone(TCFAction action);
}
private abstract class LaunchStep implements Runnable {
LaunchStep() {
launch_steps.add(this);
}
abstract void start() throws Exception;
void done() {
if (channel.getState() != IChannel.STATE_OPEN) return;
try {
launch_steps.removeFirst().start();
}
catch (Throwable x) {
channel.terminate(x);
}
}
public void run() {
done();
}
}
private static final Collection<LaunchListener> listeners = new ArrayList<LaunchListener>();
private static LaunchListener[] listeners_array;
private final Collection<ActionsListener> action_listeners = new ArrayList<ActionsListener>();
private TCFTask<Boolean> launch_task;
private IProgressMonitor launch_monitor;
private IChannel channel;
private Throwable error;
private TCFBreakpointsStatus breakpoints_status;
private String mode;
private boolean connecting;
private boolean disconnecting;
private boolean disconnected;
private boolean shutdown;
private boolean last_context_exited;
private long actions_interval;
private final LinkedList<TCFTask<Boolean>> disconnect_wait_list = new LinkedList<TCFTask<Boolean>>();
private final HashSet<Object> pending_clients = new HashSet<Object>();
private long pending_clients_timestamp;
private String peer_name;
private Runnable update_memory_maps;
private ProcessContext process;
private Collection<Map<String,Object>> process_signals;
private boolean process_exited;
private int process_exit_code;
private final HashMap<String,String> process_env = new HashMap<String,String>();
private final HashMap<String,TCFAction> active_actions = new HashMap<String,TCFAction>();
private final HashMap<String,LinkedList<TCFAction>> context_action_queue = new HashMap<String,LinkedList<TCFAction>>();
private final HashMap<String,Long> context_action_timestamps = new HashMap<String,Long>();
private final HashMap<String,ProcessContext> attached_processes = new HashMap<String,ProcessContext>();
private final HashMap<String,String> process_stream_ids = new HashMap<String,String>();
private final HashMap<String,String> uart_tx_stream_ids = new HashMap<String,String>();
private final HashMap<String,String> uart_rx_stream_ids = new HashMap<String,String>();
private final HashSet<String> disconnected_stream_ids = new HashSet<String>();
private final LinkedList<LaunchStep> launch_steps = new LinkedList<LaunchStep>();
private final LinkedList<String> redirection_path = new LinkedList<String>();
private List<IPathMap.PathMapRule> host_path_map = new ArrayList<IPathMap.PathMapRule>();
private TCFDataCache<IPathMap.PathMapRule[]> target_path_map;
private HashMap<String,IStorage> target_path_mapping_cache = new HashMap<String,IStorage>();
private final HashMap<String,TCFDataCache<String[]>> context_query_cache = new HashMap<String,TCFDataCache<String[]>>();
private Set<String> context_filter;
private String dprintf_stream_id;
private final IStreams.StreamsListener streams_listener = new IStreams.StreamsListener() {
public void created(String stream_type, String stream_id, String context_id) {
disconnected_stream_ids.remove(stream_id);
if (stream_type.equals("UART-TX")) {
uart_tx_stream_ids.put(stream_id, context_id);
readStream(context_id, stream_id, 0);
}
else if (stream_type.equals("UART-RX")) {
uart_rx_stream_ids.put(stream_id, context_id);
}
else {
process_stream_ids.put(stream_id, context_id);
disconnectUnusedStreams();
}
}
public void disposed(String stream_type, String stream_id) {
disconnected_stream_ids.add(stream_id);
}
};
private final IProcesses.ProcessesListener prs_listener = new IProcesses.ProcessesListener() {
public void exited(String process_id, int exit_code) {
if (process != null && process_id.equals(process.getID())) {
process_exit_code = exit_code;
process_exited = true;
}
attached_processes.remove(process_id);
}
};
private final IRunControl.RunControlListener rc_listener = new IRunControl.RunControlListener() {
private void flushContextQueryCache() {
for (TCFDataCache<?> c : context_query_cache.values()) c.reset();
}
public void contextAdded(RunControlContext[] contexts) {
flushContextQueryCache();
}
public void contextChanged(RunControlContext[] contexts) {
flushContextQueryCache();
}
public void contextRemoved(String[] context_ids) {
flushContextQueryCache();
}
public void contextSuspended(String context, String pc, String reason, Map<String, Object> params) {
}
public void contextResumed(String context) {
}
public void containerSuspended(String context, String pc, String reason, Map<String, Object> params, String[] suspended_ids) {
}
public void containerResumed(String[] context_ids) {
}
public void contextException(String context, String msg) {
}
};
private static LaunchListener[] getListeners() {
if (listeners_array != null) return listeners_array;
return listeners_array = listeners.toArray(new LaunchListener[listeners.size()]);
}
public TCFLaunch(ILaunchConfiguration launchConfiguration, String mode) {
super(launchConfiguration, mode, null);
for (LaunchListener l : getListeners()) l.onCreated(TCFLaunch.this);
}
private void onConnected() throws Exception {
// The method is called when TCF channel is successfully connected.
if (redirection_path.size() > 0) {
// Connected to intermediate peer (value-add).
// Redirect to next peer:
new LaunchStep() {
@Override
void start() throws Exception {
String id = redirection_path.removeFirst();
IPeer p = Protocol.getLocator().getPeers().get(id);
if (p != null) channel.redirect(p.getAttributes());
else channel.redirect(id);
if (launch_monitor != null) {
String name = null;
if (p != null) name = p.getName();
if (name == null) name = id;
launch_monitor.subTask("Connecting to " + name);
}
}
};
}
else {
final ILaunchConfiguration cfg = getLaunchConfiguration();
boolean use_context_filter =
getAttribute("attach_to_context") != null ||
getAttribute("attach_to_process") != null ||
cfg != null && cfg.getAttribute(TCFLaunchDelegate.ATTR_LOCAL_PROGRAM_FILE, "").length() > 0 ||
cfg != null && cfg.getAttribute(TCFLaunchDelegate.ATTR_REMOTE_PROGRAM_FILE, "").length() > 0;
if (cfg != null) use_context_filter = cfg.getAttribute(TCFLaunchDelegate.ATTR_USE_CONTEXT_FILTER, use_context_filter);
if (use_context_filter) context_filter = new HashSet<String>();
final IRunControl rc_service = getService(IRunControl.class);
if (rc_service != null) rc_service.addListener(rc_listener);
final IPathMap path_map_service = getService(IPathMap.class);
if (path_map_service != null) {
target_path_map = new TCFDataCache<IPathMap.PathMapRule[]>(channel) {
@Override
protected boolean startDataRetrieval() {
command = path_map_service.get(new IPathMap.DoneGet() {
public void doneGet(IToken token, Exception error, IPathMap.PathMapRule[] map) {
set(token, error, map);
}
});
return false;
}
};
path_map_service.addListener(new IPathMap.PathMapListener() {
public void changed() {
target_path_map.reset();
target_path_mapping_cache = new HashMap<String,IStorage>();
}
});
}
if (cfg != null && path_map_service != null) {
new LaunchStep() {
@Override
void start() throws Exception {
readPathMapConfiguration(cfg);
applyPathMap(this);
}
};
}
final IStreams streams = getService(IStreams.class);
if (streams != null) {
// Subscribe Streams service:
new LaunchStep() {
@Override
void start() {
final Set<IToken> cmds = new HashSet<IToken>();
String[] nms = { IProcesses.NAME, IProcessesV1.NAME, "UART-RX", "UART-TX" };
for (String s : nms) {
cmds.add(streams.subscribe(s, streams_listener, new IStreams.DoneSubscribe() {
public void doneSubscribe(IToken token, Exception error) {
cmds.remove(token);
if (error != null) channel.terminate(error);
if (cmds.size() == 0) done();
}
}));
}
if (cmds.size() == 0) done();
}
};
}
if (mode.equals(ILaunchManager.DEBUG_MODE)) {
if (context_filter != null) {
String attach_to_context = getAttribute("attach_to_context");
if (attach_to_context != null) context_filter.add(attach_to_context);
}
final IMemoryMap mem_map = channel.getRemoteService(IMemoryMap.class);
if (mem_map != null) {
// Send manual memory map items:
new LaunchStep() {
@Override
void start() throws Exception {
downloadMemoryMaps(cfg, this);
}
};
}
// Send breakpoints:
new LaunchStep() {
@Override
void start() throws Exception {
breakpoints_status = new TCFBreakpointsStatus(TCFLaunch.this);
Activator.getBreakpointsModel().downloadBreakpoints(channel, this);
}
};
final IDPrintf dprintf = getService(IDPrintf.class);
if (dprintf != null) {
// Open dprintf stream:
new LaunchStep() {
@Override
void start() throws Exception {
dprintf.open(null, new IDPrintf.DoneCommandOpen() {
@Override
public void doneCommandOpen(IToken token, Exception error, final String id) {
if (error != null) {
channel.terminate(error);
return;
}
dprintf_stream_id = id;
streams.connect(id, new IStreams.DoneConnect() {
@Override
public void doneConnect(IToken token, Exception error) {
if (error != null) {
channel.terminate(error);
return;
}
readStream(null, id, 0);
done();
}
});
}
});
}
};
}
}
if (cfg != null && getService(IMemory.class) != null) {
String s = cfg.getAttribute(TCFLaunchDelegate.ATTR_FILES, (String)null);
if (s != null) {
@SuppressWarnings("unchecked")
Collection<Map<String,Object>> c = (Collection<Map<String,Object>>)JSON.parseOne(s.getBytes("UTF-8"));
final ElfLoader loader = new ElfLoader(channel);
for (final Map<String,Object> m : c) {
Boolean b1 = (Boolean)m.get(TCFLaunchDelegate.FILES_DOWNLOAD);
Boolean b2 = (Boolean)m.get(TCFLaunchDelegate.FILES_SET_PC);
if (b1 != null && b1.booleanValue() || b2 != null && b2.booleanValue()) {
new LaunchStep() {
@Override
void start() throws Exception {
loader.load(m, this);
}
};
}
}
new LaunchStep() {
@Override
void start() throws Exception {
loader.dispose();
done();
}
};
}
}
// Call client launch sequence:
new LaunchStep() {
@Override
void start() {
runLaunchSequence(this);
}
};
if (cfg != null) startRemoteProcess(cfg);
// Final launch step.
// Notify clients:
new LaunchStep() {
@Override
void start() {
connecting = false;
disconnectUnusedStreams();
for (LaunchListener l : getListeners()) l.onConnected(TCFLaunch.this);
fireChanged();
if (launch_task != null) launch_task.done(true);
launch_monitor = null;
launch_task = null;
}
};
}
launch_steps.removeFirst().start();
}
private void onDisconnected(Throwable error) {
// The method is called when TCF channel is closed.
assert !disconnected;
assert !shutdown;
this.error = error;
breakpoints_status = null;
connecting = false;
disconnected = true;
for (LaunchListener l : getListeners()) l.onDisconnected(this);
for (TCFDataCache<?> c : context_query_cache.values()) c.dispose();
context_query_cache.clear();
if (DebugPlugin.getDefault() != null) fireChanged();
if (launch_task != null) launch_task.done(false);
launch_monitor = null;
launch_task = null;
runShutdownSequence(new Runnable() {
public void run() {
shutdown = true;
fireTerminate();
for (TCFTask<Boolean> tsk : disconnect_wait_list) {
tsk.done(Boolean.TRUE);
}
disconnect_wait_list.clear();
}
});
// Log severe exceptions: bug 386067
if (error instanceof RuntimeException) {
Activator.log("Channel disconnected with error", error);
}
}
protected void runLaunchSequence(Runnable done) {
done.run();
}
private void downloadMemoryMaps(ILaunchConfiguration cfg, final Runnable done) throws Exception {
final IMemoryMap mmap = channel.getRemoteService(IMemoryMap.class);
if (mmap == null) {
done.run();
return;
}
final HashMap<String,ArrayList<IMemoryMap.MemoryRegion>> maps = new HashMap<String,ArrayList<IMemoryMap.MemoryRegion>>();
getMemMaps(maps, cfg);
final HashSet<IToken> cmds = new HashSet<IToken>(); // Pending commands
final Runnable done_all = new Runnable() {
boolean launch_done;
public void run() {
if (launch_done) return;
done.run();
launch_done = true;
}
};
final IMemoryMap.DoneSet done_set_mmap = new IMemoryMap.DoneSet() {
public void doneSet(IToken token, Exception error) {
assert cmds.contains(token);
cmds.remove(token);
if (error != null) Activator.log("Cannot update context memory map", error);
if (cmds.isEmpty()) done_all.run();
}
};
for (String id : maps.keySet()) {
ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id);
TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]);
cmds.add(mmap.set(id, arr, done_set_mmap));
}
update_memory_maps = new Runnable() {
public void run() {
try {
Set<String> set = new HashSet<String>(maps.keySet());
maps.clear();
getMemMaps(maps, getLaunchConfiguration());
for (String id : maps.keySet()) {
ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id);
TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]);
cmds.add(mmap.set(id, arr, done_set_mmap));
}
for (String id : set) {
if (maps.get(id) != null) continue;
cmds.add(mmap.set(id, null, done_set_mmap));
}
}
catch (Throwable x) {
channel.terminate(x);
}
}
};
if (cmds.isEmpty()) done_all.run();
}
@SuppressWarnings("unchecked")
private void getMemMaps(Map<String,ArrayList<IMemoryMap.MemoryRegion>> maps, ILaunchConfiguration cfg) throws Exception {
// Parse ATTR_FILES
String s = cfg.getAttribute(TCFLaunchDelegate.ATTR_FILES, (String)null);
if (s != null) {
Collection<Map<String,Object>> c = (Collection<Map<String,Object>>)JSON.parseOne(s.getBytes("UTF-8"));
for (Map<String,Object> m : c) {
Boolean b = (Boolean)m.get(TCFLaunchDelegate.FILES_LOAD_SYMBOLS);
if (b != null && b.booleanValue()) {
String id = (String)m.get(TCFLaunchDelegate.FILES_CONTEXT_ID);
if (id == null) id = (String)m.get(TCFLaunchDelegate.FILES_CONTEXT_FULL_NAME);
if (id != null) {
Map<String,Object> map = new HashMap<String,Object>();
map.put(IMemoryMap.PROP_FILE_NAME, m.get(TCFLaunchDelegate.FILES_FILE_NAME));
b = (Boolean)m.get(TCFLaunchDelegate.FILES_RELOCATE);
if (b != null && b.booleanValue()) {
map.put(IMemoryMap.PROP_ADDRESS, m.get(TCFLaunchDelegate.FILES_ADDRESS));
map.put(IMemoryMap.PROP_OFFSET, m.get(TCFLaunchDelegate.FILES_OFFSET));
map.put(IMemoryMap.PROP_SIZE, m.get(TCFLaunchDelegate.FILES_SIZE));
}
b = (Boolean)m.get(TCFLaunchDelegate.FILES_ENABLE_OSA);
if (b != null && b.booleanValue()) {
map.put(IMemoryMap.PROP_OSA, new HashMap<String,Object>());
}
ArrayList<IMemoryMap.MemoryRegion> l = maps.get(id);
if (l == null) {
l = new ArrayList<IMemoryMap.MemoryRegion>();
maps.put(id, l);
}
l.add(new TCFMemoryRegion(map));
}
}
}
}
// Parse ATTR_MEMORY_MAP
TCFLaunchDelegate.getMemMapsAttribute(maps, cfg);
}
private void readPathMapConfiguration(ILaunchConfiguration cfg) throws CoreException {
String s = cfg.getAttribute(TCFLaunchDelegate.ATTR_PATH_MAP, "");
host_path_map = new ArrayList<IPathMap.PathMapRule>();
host_path_map.addAll(TCFLaunchDelegate.parsePathMapAttribute(s));
s = cfg.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, "");
host_path_map.addAll(TCFLaunchDelegate.parseSourceLocatorMemento(s));
}
/**
* Returns the TCF client ID of this instance of Eclipse.
* @return The client ID.
*/
public String getClientID() {
return Activator.getClientID();
}
/**
* Apply the path map to this launch channel.
* @param done The done to invoke.
*/
protected void applyPathMap(final Runnable done) {
IPathMap path_map_service = getService(IPathMap.class);
if (path_map_service == null) {
if (done != null) done.run();
return;
}
path_map_service.set(host_path_map.toArray(new IPathMap.PathMapRule[host_path_map.size()]), new IPathMap.DoneSet() {
@Override
public void doneSet(IToken token, Exception error) {
if (error != null) channel.terminate(error);
else if (done != null) done.run();
}
});
}
private String[] toArgsArray(String file, String cmd) {
// Create arguments list from a command line.
int i = 0;
int l = cmd.length();
List<String> arr = new ArrayList<String>();
arr.add(file);
for (;;) {
while (i < l && cmd.charAt(i) == ' ') i++;
if (i >= l) break;
String s = null;
if (cmd.charAt(i) == '"') {
i++;
StringBuffer bf = new StringBuffer();
while (i < l) {
char ch = cmd.charAt(i++);
if (ch == '"') break;
if (ch == '\\' && i < l) ch = cmd.charAt(i++);
bf.append(ch);
}
s = bf.toString();
}
else {
int i0 = i;
while (i < l && cmd.charAt(i) != ' ') i++;
s = cmd.substring(i0, i);
}
arr.add(s);
}
return arr.toArray(new String[arr.size()]);
}
private void copyFileToRemoteTarget(String local_file, String remote_file, final Runnable done) {
if (local_file == null) {
channel.terminate(new Exception("Program does not exist"));
return;
}
final IFileSystem fs = channel.getRemoteService(IFileSystem.class);
if (fs == null) {
channel.terminate(new Exception(
"Cannot download program file: target does not provide File System service"));
return;
}
try {
final File local_fd = new File(local_file);
final InputStream inp = new FileInputStream(local_fd);
final String task_name = "Downloading: " + local_fd.getName();
int flags = IFileSystem.TCF_O_WRITE | IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_TRUNC;
if (launch_monitor != null) launch_monitor.subTask(task_name);
fs.open(remote_file, flags, null, new IFileSystem.DoneOpen() {
IFileHandle handle;
long offset = 0;
final Set<IToken> cmds = new HashSet<IToken>();
final byte[] buf = new byte[0x1000];
public void doneOpen(IToken token, FileSystemException error, IFileHandle handle) {
this.handle = handle;
if (error != null) {
TCFLaunch.this.error = new Exception("Cannot download program file", error);
fireChanged();
done.run();
}
else {
write_next();
}
}
private void write_next() {
try {
while (cmds.size() < 8) {
int rd = inp.read(buf);
if (rd < 0) {
close();
break;
}
final long kb_done = (offset + rd) / 1024;
cmds.add(fs.write(handle, offset, buf, 0, rd, new IFileSystem.DoneWrite() {
public void doneWrite(IToken token, FileSystemException error) {
cmds.remove(token);
if (launch_monitor != null) {
launch_monitor.subTask(task_name + ", " + kb_done + " KB done");
}
if (error != null) channel.terminate(error);
else write_next();
}
}));
offset += rd;
}
}
catch (Throwable x) {
channel.terminate(x);
}
}
private void close() {
if (cmds.size() > 0) return;
try {
inp.close();
fs.close(handle, new IFileSystem.DoneClose() {
public void doneClose(IToken token, FileSystemException error) {
if (error != null) channel.terminate(error);
else done.run();
}
});
}
catch (Throwable x) {
channel.terminate(x);
}
}
});
}
catch (Throwable x) {
channel.terminate(x);
}
}
private void startRemoteProcess(final ILaunchConfiguration cfg) throws Exception {
final String project = cfg.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_NAME, "");
final String local_file = cfg.getAttribute(TCFLaunchDelegate.ATTR_LOCAL_PROGRAM_FILE, "");
final String remote_file = cfg.getAttribute(TCFLaunchDelegate.ATTR_REMOTE_PROGRAM_FILE, "");
if (local_file.length() != 0 && remote_file.length() != 0) {
// Download executable file
new LaunchStep() {
@Override
void start() throws Exception {
copyFileToRemoteTarget(TCFLaunchDelegate.getProgramPath(project, local_file), remote_file, this);
}
};
}
final String attach_to_process = getAttribute("attach_to_process");
if (attach_to_process != null) {
final IProcesses ps = channel.getRemoteService(IProcesses.class);
if (ps == null) throw new Exception("Target does not provide Processes service");
// Attach the process
new LaunchStep() {
@Override
void start() {
IProcesses.DoneGetContext done = new IProcesses.DoneGetContext() {
public void doneGetContext(IToken token, final Exception error, final ProcessContext process) {
if (error != null) {
channel.terminate(error);
}
else {
process.attach(new IProcesses.DoneCommand() {
public void doneCommand(IToken token, final Exception error) {
if (error != null) {
channel.terminate(error);
}
else {
TCFLaunch.this.process = process;
ps.addListener(prs_listener);
onAttach(process);
done();
}
}
});
}
}
};
ps.getContext(attach_to_process, done);
}
};
}
else if (local_file.length() != 0 || remote_file.length() != 0) {
final IProcesses ps = channel.getRemoteService(IProcesses.class);
if (ps == null) throw new Exception("Target does not provide Processes service");
final boolean append = cfg.getAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true);
if (append) {
// Get system environment variables
new LaunchStep() {
@Override
void start() throws Exception {
ps.getEnvironment(new IProcesses.DoneGetEnvironment() {
public void doneGetEnvironment(IToken token, Exception error, Map<String,String> env) {
if (error != null) {
channel.terminate(error);
}
else {
if (env != null) process_env.putAll(env);
done();
}
}
});
}
};
}
final String dir = cfg.getAttribute(TCFLaunchDelegate.ATTR_WORKING_DIRECTORY, "");
final String args = cfg.getAttribute(TCFLaunchDelegate.ATTR_PROGRAM_ARGUMENTS, "");
final Map<String,String> env = cfg.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, (Map<String,String>)null);
final boolean attach_children = cfg.getAttribute(TCFLaunchDelegate.ATTR_ATTACH_CHILDREN, true);
final boolean stop_at_entry = cfg.getAttribute(TCFLaunchDelegate.ATTR_STOP_AT_ENTRY, true);
final boolean stop_at_main = cfg.getAttribute(TCFLaunchDelegate.ATTR_STOP_AT_MAIN, true);
final boolean use_terminal = cfg.getAttribute(TCFLaunchDelegate.ATTR_USE_TERMINAL, true);
final Set<Integer> dont_stop = TCFLaunchDelegate.readSigSet(cfg.getAttribute(TCFLaunchDelegate.ATTR_SIGNALS_DONT_STOP, ""));
final Set<Integer> dont_pass = TCFLaunchDelegate.readSigSet(cfg.getAttribute(TCFLaunchDelegate.ATTR_SIGNALS_DONT_PASS, ""));
final IProcessesV1 ps_v1 = channel.getRemoteService(IProcessesV1.class);
// Start the process
new LaunchStep() {
@Override
void start() throws Exception {
if (env != null) {
for (Map.Entry<String,String> e : env.entrySet()) {
String key = e.getKey();
String val = e.getValue();
if (val == null) {
process_env.remove(key);
}
else {
val = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(val);
process_env.put(key, val);
}
}
}
String file = remote_file;
if (file == null || file.length() == 0) file = TCFLaunchDelegate.getProgramPath(project, local_file);
if (file == null || file.length() == 0) {
channel.terminate(new Exception("Program file does not exist"));
return;
}
IProcesses.DoneStart done = new IProcesses.DoneStart() {
public void doneStart(IToken token, final Exception error, ProcessContext process) {
if (error != null) {
Protocol.sync(new Runnable() {
public void run() {
channel.terminate(error);
}
});
}
else {
TCFLaunch.this.process = process;
ps.addListener(prs_listener);
onAttach(process);
done();
}
}
};
if (launch_monitor != null) launch_monitor.subTask("Starting: " + file);
String cmd = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(args);
String[] args_arr = toArgsArray(file, cmd);
if (ps_v1 != null) {
Map<String,Object> params = new HashMap<String,Object>();
if (mode.equals(ILaunchManager.DEBUG_MODE)) {
params.put(IProcessesV1.START_ATTACH, true);
params.put(IProcessesV1.START_ATTACH_CHILDREN, attach_children);
params.put(IProcessesV1.START_STOP_AT_ENTRY, stop_at_entry);
params.put(IProcessesV1.START_STOP_AT_MAIN, stop_at_main);
if (dont_stop.size() > 0) params.put(IProcessesV1.START_SIG_DONT_STOP, dont_stop);
if (dont_pass.size() > 0) params.put(IProcessesV1.START_SIG_DONT_PASS, dont_pass);
}
if (use_terminal) params.put(IProcessesV1.START_USE_TERMINAL, true);
ps_v1.start(dir, file, args_arr, process_env, params, done);
}
else {
boolean attach = mode.equals(ILaunchManager.DEBUG_MODE);
ps.start(dir, file, args_arr, process_env, attach, done);
}
}
};
if (mode.equals(ILaunchManager.DEBUG_MODE)) {
// Get process signal list
new LaunchStep() {
@Override
void start() {
ps.getSignalList(process.getID(), new IProcesses.DoneGetSignalList() {
public void doneGetSignalList(IToken token, Exception error, Collection<Map<String,Object>> list) {
if (error != null && attached_processes.get(process.getID()) != null) {
Activator.log("Can't get process signal list", error);
}
process_signals = list;
done();
}
});
}
};
// Set process signal masks
if (ps_v1 == null && (dont_stop.size() > 0 || dont_pass.size() > 0)) {
new LaunchStep() {
@Override
void start() {
final HashSet<IToken> cmds = new HashSet<IToken>();
final IProcesses.DoneCommand done_set_mask = new IProcesses.DoneCommand() {
public void doneCommand(IToken token, Exception error) {
cmds.remove(token);
if (error != null && attached_processes.size() > 0) channel.terminate(error);
else if (cmds.size() == 0) done();
}
};
cmds.add(ps.setSignalMask(process.getID(), dont_stop, dont_pass, done_set_mask));
final IRunControl rc = channel.getRemoteService(IRunControl.class);
if (rc != null) {
final IRunControl.DoneGetChildren done_get_children = new IRunControl.DoneGetChildren() {
public void doneGetChildren(IToken token, Exception error, String[] context_ids) {
if (context_ids != null) {
for (String id : context_ids) {
cmds.add(ps.setSignalMask(id, dont_stop, dont_pass, done_set_mask));
cmds.add(rc.getChildren(id, this));
}
}
cmds.remove(token);
if (error != null && attached_processes.size() > 0) channel.terminate(error);
else if (cmds.size() == 0) done();
}
};
cmds.add(rc.getChildren(process.getID(), done_get_children));
}
}
};
}
}
}
}
private void readProcessStreams(ProcessContext ctx) {
assert attached_processes.get(ctx.getID()) == ctx;
IStreams streams = getService(IStreams.class);
if (streams == null) return;
String out_id = (String)ctx.getProperties().get(IProcesses.PROP_STDOUT_ID);
String err_id = (String)ctx.getProperties().get(IProcesses.PROP_STDERR_ID);
if (process_stream_ids.get(out_id) != null) readStream(ctx.getID(), out_id, 0);
if (process_stream_ids.get(err_id) != null) readStream(ctx.getID(), err_id, 0);
}
private void disconnectUnusedStreams() {
if (connecting) return;
HashSet<String> set = new HashSet<String>();
for (ProcessContext ctx : attached_processes.values()) {
set.add((String)ctx.getProperties().get(IProcesses.PROP_STDIN_ID));
set.add((String)ctx.getProperties().get(IProcesses.PROP_STDOUT_ID));
set.add((String)ctx.getProperties().get(IProcesses.PROP_STDERR_ID));
}
for (String id : process_stream_ids.keySet().toArray(new String[process_stream_ids.size()])) {
if (!set.contains(id)) disconnectStream(id);
}
}
private void readStream(final String ctx_id, final String id, final int no) {
if (ctx_id != null) {
// Force creation of console
for (LaunchListener l : getListeners()) l.onProcessOutput(this, ctx_id, no, null);
}
final IStreams streams = getService(IStreams.class);
IStreams.DoneRead done = new IStreams.DoneRead() {
public void doneRead(IToken token, Exception error, int lost_size, byte[] data, boolean eos) {
if (lost_size > 0) {
Exception x = new IOException("Process output data lost due buffer overflow");
for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, ctx_id, no, x, lost_size);
}
if (data != null && data.length > 0) {
for (LaunchListener l : getListeners()) l.onProcessOutput(TCFLaunch.this, ctx_id, no, data);
}
if (disconnected_stream_ids.contains(id)) return;
if (error != null) {
for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, ctx_id, no, error, 0);
disconnected_stream_ids.add(id);
}
if (!eos && error == null) {
streams.read(id, 0x1000, this);
}
}
};
streams.read(id, 0x1000, done);
streams.read(id, 0x1000, done);
streams.read(id, 0x1000, done);
streams.read(id, 0x1000, done);
}
private void disconnectStream(String id) {
assert process_stream_ids.get(id) != null;
process_stream_ids.remove(id);
if (channel.getState() != IChannel.STATE_OPEN) return;
disconnected_stream_ids.add(id);
IStreams streams = getService(IStreams.class);
streams.disconnect(id, new IStreams.DoneDisconnect() {
public void doneDisconnect(IToken token, Exception error) {
if (channel.getState() != IChannel.STATE_OPEN) return;
if (error != null) channel.terminate(error);
}
});
}
protected void runShutdownSequence(final Runnable done) {
done.run();
}
/*--------------------------------------------------------------------------------------------*/
/**
* Return error object if launching failed.
*/
public Throwable getError() {
return error;
}
/**
* Terminate the launch because of fatal error.
* @param x - the error object.
*/
public void setError(Throwable x) {
error = x;
if (x != null) {
if (channel != null && channel.getState() == IChannel.STATE_OPEN) {
channel.terminate(x);
}
else if (!connecting) {
disconnected = true;
shutdown = true;
}
}
fireChanged();
}
/**
* Get current target breakpoints status information.
* @return status information object
*/
public TCFBreakpointsStatus getBreakpointsStatus() {
return breakpoints_status;
}
/**
* Check if the agent supports setting of user defined memory map entries
* for a context that does not exits yet.
* @return true if memory map preloading is supported.
*/
public boolean isMemoryMapPreloadingSupported() {
return true;
}
/**
* Register a launch listener.
* @param listener - client object implementing TCFLaunch.LaunchListener interface.
*/
public static void addListener(LaunchListener listener) {
assert Protocol.isDispatchThread();
listeners.add(listener);
listeners_array = null;
}
/**
* Remove a launch listener.
* @param listener - client object implementing TCFLaunch.LaunchListener interface.
*/
public static void removeListener(LaunchListener listener) {
assert Protocol.isDispatchThread();
listeners.remove(listener);
listeners_array = null;
}
@Override
public void launchConfigurationChanged(final ILaunchConfiguration cfg) {
super.launchConfigurationChanged(cfg);
if (!cfg.equals(getLaunchConfiguration())) return;
if (channel != null && channel.getState() == IChannel.STATE_OPEN && !connecting) {
new TCFTask<Boolean>(channel) {
public void run() {
try {
if (update_memory_maps != null) update_memory_maps.run();
readPathMapConfiguration(cfg);
applyPathMap(new Runnable() {
public void run() {
done(false);
}
});
}
catch (Throwable x) {
channel.terminate(x);
done(false);
}
}
}.getE();
// TODO: update signal masks when launch configuration changes
}
}
@Override
public void launchRemoved(ILaunch launch) {
if (this != launch) return;
super.launchRemoved(launch);
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
if (channel != null && channel.getState() != IChannel.STATE_CLOSED) {
channel.close();
}
}
});
}
/**
* Get TCF communication channel that is used by the launch.
* Thread safe method.
*/
public IChannel getChannel() {
return channel;
}
/**
* If the launch has started a remote process, return the process information.
* Starting a process is optional and not applicable to all launches.
* @return remote process information or null.
*/
public IProcesses.ProcessContext getProcessContext() {
return process;
}
/**
* Write to stdin stream of remote process that was started by the launch.
* @param prs_id - TCF ID of the process.
* @param buf - data to write
* @param pos - starting position in 'buf'
* @param len - number of bytes to write.
* @throws Exception
*/
public void writeProcessInputStream(final String prs_id, byte[] buf, int pos, final int len) throws Exception {
assert Protocol.isDispatchThread();
if (channel.getState() != IChannel.STATE_OPEN) throw new IOException("Connection closed");
IStreams streams = getService(IStreams.class);
if (streams == null) throw new IOException("Streams service not available");
ProcessContext ctx = attached_processes.get(prs_id);
if (ctx != null) {
final String id = (String)ctx.getProperties().get(IProcesses.PROP_STDIN_ID);
if (process_stream_ids.get(id) == null) throw new IOException("Input stream not available");
streams.write(id, buf, pos, len, new IStreams.DoneWrite() {
public void doneWrite(IToken token, Exception error) {
if (error == null) return;
if (process_stream_ids.get(id) == null) return;
for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, prs_id, 0, error, len);
disconnectStream(id);
}
});
return;
}
for (final String rx_id : uart_rx_stream_ids.keySet()) {
if (!prs_id.equals(uart_rx_stream_ids.get(rx_id))) continue;
streams.write(rx_id, buf, pos, len, new IStreams.DoneWrite() {
public void doneWrite(IToken token, Exception error) {
if (error == null) return;
if (uart_rx_stream_ids.get(rx_id) == null) return;
for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, prs_id, 0, error, len);
disconnectStream(rx_id);
}
});
return;
}
throw new IOException("No target process");
}
public void openUartStreams(final String ctx_id, Map<String,Object> uart_props) {
assert Protocol.isDispatchThread();
if (uart_props == null) return;
IStreams streams = getService(IStreams.class);
if (streams == null) return;
final String rx_id = (String)uart_props.get("RXStreamID");
if (rx_id != null && uart_rx_stream_ids.get(rx_id) == null) {
streams.connect(rx_id, new IStreams.DoneConnect() {
@Override
public void doneConnect(IToken token, Exception error) {
if (uart_rx_stream_ids.get(rx_id) != null) return;
uart_rx_stream_ids.put(rx_id, ctx_id);
if (error == null) return;
for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, ctx_id, 0, error, 0);
}
});
}
final String tx_id = (String)uart_props.get("TXStreamID");
if (tx_id != null && uart_tx_stream_ids.get(tx_id) == null) {
streams.connect(tx_id, new IStreams.DoneConnect() {
@Override
public void doneConnect(IToken token, Exception error) {
if (uart_tx_stream_ids.get(tx_id) != null) return;
uart_tx_stream_ids.put(tx_id, ctx_id);
if (error == null) {
readStream(ctx_id, tx_id, 0);
return;
}
for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, ctx_id, 0, error, 0);
}
});
}
}
public boolean isConnecting() {
return connecting;
}
public boolean isConnected() {
return channel != null && !connecting && !disconnected;
}
public void onAttach(ProcessContext ctx) {
if (context_filter != null) context_filter.add(ctx.getID());
attached_processes.put(ctx.getID(), ctx);
readProcessStreams(ctx);
}
public void onDetach(String prs_id) {
disconnectUnusedStreams();
}
public void onLastContextRemoved() {
ILaunchConfiguration cfg = getLaunchConfiguration();
try {
if (process != null && cfg.getAttribute(TCFLaunchDelegate.ATTR_DISCONNECT_ON_CTX_EXIT, true)) {
last_context_exited = true;
closeChannel();
}
}
catch (Throwable e) {
Activator.log("Cannot access launch configuration", e);
}
}
public void closeChannel() {
assert Protocol.isDispatchThread();
if (channel == null) return;
if (channel.getState() == IChannel.STATE_CLOSED) return;
if (disconnecting) return;
disconnecting = true;
final Set<IToken> cmds = new HashSet<IToken>();
for (ProcessContext ctx : attached_processes.values()) {
cmds.add(ctx.terminate(new IProcesses.DoneCommand() {
public void doneCommand(IToken token, Exception error) {
cmds.remove(token);
if (error != null) channel.terminate(error);
else if (cmds.isEmpty()) channel.close();
}
}));
}
if (channel.getState() == IChannel.STATE_OPEN) {
IStreams streams = getService(IStreams.class);
IStreams.DoneDisconnect done_disconnect = new IStreams.DoneDisconnect() {
public void doneDisconnect(IToken token, Exception error) {
cmds.remove(token);
if (error != null) channel.terminate(error);
else if (cmds.isEmpty()) channel.close();
}
};
for (String id : process_stream_ids.keySet()) {
cmds.add(streams.disconnect(id, done_disconnect));
}
for (String id : uart_rx_stream_ids.keySet()) {
cmds.add(streams.disconnect(id, done_disconnect));
}
for (String id : uart_tx_stream_ids.keySet()) {
cmds.add(streams.disconnect(id, done_disconnect));
}
process_stream_ids.clear();
uart_rx_stream_ids.clear();
uart_tx_stream_ids.clear();
if (dprintf_stream_id != null) {
disconnected_stream_ids.add(dprintf_stream_id);
cmds.add(streams.disconnect(dprintf_stream_id, done_disconnect));
dprintf_stream_id = null;
}
}
if (cmds.isEmpty()) channel.close();
}
public IPeer getPeer() {
assert Protocol.isDispatchThread();
return channel.getRemotePeer();
}
public String getPeerName() {
// Safe to call from any thread.
return peer_name;
}
/**
* Returns the name for the given peer. Overwrite to customize
* the peer name shown.
*
* @param peer The peer. Must not be <code>null</code>.
* @return The peer name. Must be never <code>null</code>.
*/
protected String getPeerName(IPeer peer) {
assert Protocol.isDispatchThread();
assert peer != null;
return peer.getName();
}
public <V extends IService> V getService(Class<V> cls) {
assert Protocol.isDispatchThread();
return channel.getRemoteService(cls);
}
@Override
public boolean canDisconnect() {
return !disconnected;
}
@Override
public boolean isDisconnected() {
return disconnected;
}
@Override
public void disconnect() throws DebugException {
try {
new TCFTask<Boolean>(8000) {
public void run() {
if (channel == null || shutdown) {
done(true);
}
else {
disconnect_wait_list.add(this);
closeChannel();
}
}
}.get();
}
catch (IllegalStateException x) {
// Don't report this exception - it means Eclipse is being shut down
}
catch (Exception x) {
throw new DebugException(new TCFError(x));
}
}
@Override
public boolean canTerminate() {
try {
return new TCFTask<Boolean>(8000) {
public void run() {
done(!disconnected && process != null && process.canTerminate());
}
}.get();
}
catch (Exception x) {
return false;
}
}
@Override
public boolean isTerminated() {
return disconnected;
}
@Override
public void terminate() throws DebugException {
try {
new TCFTask<Boolean>(8000) {
boolean detached;
boolean terminated;
public void run() {
if (disconnected || process == null || !process.canTerminate()) {
done(false);
return;
}
if (!detached && process.isAttached()) {
process.detach(new IProcesses.DoneCommand() {
@Override
public void doneCommand(IToken token, Exception error) {
if (error != null) {
error(error);
}
else {
detached = true;
run();
}
}
});
return;
}
if (!terminated) {
process.terminate(new IProcesses.DoneCommand() {
@Override
public void doneCommand(IToken token, Exception error) {
if (error != null) {
error(error);
}
else {
terminated = true;
run();
}
}
});
return;
}
if (channel == null || shutdown) {
done(true);
}
else {
disconnect_wait_list.add(this);
closeChannel();
}
}
}.get();
}
catch (Exception x) {
throw new DebugException(new TCFError(x));
}
}
public boolean isExited() {
return last_context_exited;
}
public boolean isProcessExited() {
return process_exited;
}
public int getExitCode() {
return process_exit_code;
}
public Collection<Map<String,Object>> getSignalList() {
return process_signals;
}
public List<IPathMap.PathMapRule> getHostPathMap() {
assert Protocol.isDispatchThread();
return host_path_map;
}
public TCFDataCache<IPathMap.PathMapRule[]> getTargetPathMap() {
assert Protocol.isDispatchThread();
return target_path_map;
}
public Map<String,IStorage> getTargetPathMappingCache() {
return target_path_mapping_cache;
}
public TCFDataCache<String[]> getContextQuery(final String query) {
if (query == null) return null;
TCFDataCache<String[]> cache = context_query_cache.get(query);
if (cache == null) {
if (disconnected) return null;
final IContextQuery service = channel.getRemoteService(IContextQuery.class);
if (service == null) return null;
cache = new TCFDataCache<String[]>(channel) {
@Override
protected boolean startDataRetrieval() {
command = service.query(query, new IContextQuery.DoneQuery() {
public void doneQuery(IToken token, Exception error, String[] contexts) {
set(token, error, contexts);
}
});
return false;
}
};
context_query_cache.put(query, cache);
}
return cache;
}
/**
* Activate TCF launch: open communication channel and perform all necessary launch steps.
* @param mode - on of launch mode constants defined in ILaunchManager.
* @param id - TCF peer ID.
*/
public void launchTCF(String mode, String id) {
launchTCF(mode, id, null, null);
}
/**
* Activate TCF launch: open communication channel and perform all necessary launch steps.
* @param mode - on of launch mode constants defined in ILaunchManager.
* @param id - TCF peer ID.
* @param task - TCF task that is waiting until the launching is done, can be null
* @param monitor - launching progress monitor, can be null
*/
public void launchTCF(String mode, String id, TCFTask<Boolean> task, IProgressMonitor monitor) {
assert Protocol.isDispatchThread();
this.mode = mode;
this.launch_task = task;
this.launch_monitor = monitor;
try {
if (id == null || id.length() == 0) throw new IOException("Invalid peer ID");
redirection_path.clear();
for (;;) {
int i = id.indexOf('/');
if (i <= 0) {
redirection_path.add(id);
break;
}
redirection_path.add(id.substring(0, i));
id = id.substring(i + 1);
}
String id0 = redirection_path.removeFirst();
IPeer peer = Protocol.getLocator().getPeers().get(id0);
if (peer == null) throw new Exception("Cannot locate peer " + id0);
peer_name = getPeerName(peer);
channel = peer.openChannel();
channel.addChannelListener(new IChannel.IChannelListener() {
public void onChannelOpened() {
try {
peer_name = getPeerName(getPeer());
onConnected();
}
catch (Throwable x) {
channel.terminate(x);
}
}
public void congestionLevel(int level) {
}
public void onChannelClosed(Throwable error) {
channel.removeChannelListener(this);
onDisconnected(error);
}
});
assert channel.getState() == IChannel.STATE_OPENING;
if (launch_monitor != null) launch_monitor.subTask("Connecting to " + peer_name);
connecting = true;
}
catch (Throwable e) {
onDisconnected(e);
}
}
/**
* Activate TCF launch: Re-use the passed in communication channel and perform all necessary launch steps.
*
* @param mode - on of launch mode constants defined in ILaunchManager.
* @param peer_name - TCF peer name.
* @param channel - TCF communication channel.
*/
public void launchTCF(String mode, String peer_name, IChannel channel) {
assert Protocol.isDispatchThread();
this.mode = mode;
this.redirection_path.clear();
try {
if (channel == null || channel.getRemotePeer() == null) throw new IOException("Invalid channel");
this.peer_name = peer_name;
this.channel = channel;
IChannel.IChannelListener listener = new IChannel.IChannelListener() {
public void onChannelOpened() {
try {
TCFLaunch.this.peer_name = getPeerName(getPeer());
onConnected();
}
catch (Throwable x) {
TCFLaunch.this.channel.terminate(x);
}
}
public void congestionLevel(int level) {
}
public void onChannelClosed(Throwable error) {
TCFLaunch.this.channel.removeChannelListener(this);
onDisconnected(error);
}
};
channel.addChannelListener(listener);
connecting = true;
if (channel.getState() == IChannel.STATE_OPEN) {
listener.onChannelOpened();
}
else if (channel.getState() != IChannel.STATE_OPENING) {
throw new IOException("Channel is in invalid state");
}
}
catch (Throwable e) {
onDisconnected(e);
}
}
/****************************************************************************************************************/
private long getActionTimeStamp(String id) {
Long l = context_action_timestamps.get(id);
if (l == null) return 0;
return l.longValue();
}
private void startAction(final String id) {
if (active_actions.get(id) != null) return;
LinkedList<TCFAction> list = context_action_queue.get(id);
if (list == null || list.size() == 0) return;
final TCFAction action = list.removeFirst();
if (list.size() == 0) context_action_queue.remove(id);
active_actions.put(id, action);
final long timestamp = getActionTimeStamp(id);
long time = System.currentTimeMillis();
Protocol.invokeLater(timestamp + actions_interval - time, new Runnable() {
public void run() {
if (active_actions.get(id) != action) return;
long time = System.currentTimeMillis();
synchronized (pending_clients) {
if (pending_clients.size() > 0) {
if (time - timestamp < actions_interval + 1000) {
Protocol.invokeLater(20, this);
return;
}
pending_clients.clear();
}
else if (time < pending_clients_timestamp + 10) {
Protocol.invokeLater(pending_clients_timestamp + 10 - time, this);
return;
}
}
context_action_timestamps.put(id, time);
for (ActionsListener l : action_listeners) l.onContextActionStart(action);
action.run();
}
});
}
/**
* Add an object to the set of pending clients.
* Actions execution will be delayed until the set is empty,
* but not longer then 1 second.
* @param client
*/
public void addPendingClient(Object client) {
synchronized (pending_clients) {
pending_clients.add(client);
pending_clients_timestamp = System.currentTimeMillis();
}
}
/**
* Remove an object from the set of pending clients.
* Actions execution resumes when the set becomes empty.
* @param client
*/
public void removePendingClient(Object client) {
synchronized (pending_clients) {
if (pending_clients.remove(client) && pending_clients.size() == 0) {
pending_clients_timestamp = System.currentTimeMillis();
}
}
}
/**
* Set minimum interval between context actions execution.
* @param interval - minimum interval in milliseconds.
*/
public void setContextActionsInterval(long interval) {
actions_interval = interval;
}
/**
* Add a context action to actions queue.
* Examples of context actions are resume/suspend/step commands,
* which were requested by a user.
* @param action
*/
public void addContextAction(TCFAction action) {
assert Protocol.isDispatchThread();
String id = action.getContextID();
LinkedList<TCFAction> list = context_action_queue.get(id);
if (list == null) context_action_queue.put(id, list = new LinkedList<TCFAction>());
int priority = action.getPriority();
for (ListIterator<TCFAction> i = list.listIterator();;) {
if (i.hasNext()) {
if (priority <= i.next().getPriority()) continue;
i.previous();
}
i.add(action);
break;
}
startAction(id);
}
/**
* Set action result for given context ID.
* Action results are usually presented to a user same way as context suspend reasons.
* @param id - debug context ID.
* @param result - a string to be shown to user.
*/
public void setContextActionResult(String id, String result) {
assert Protocol.isDispatchThread();
for (ActionsListener l : action_listeners) l.onContextActionResult(id, result);
}
/**
* Remove an action from the queue.
* The method should be called when the action execution is done.
* @param action
*/
public void removeContextAction(TCFAction action) {
assert Protocol.isDispatchThread();
String id = action.getContextID();
assert active_actions.get(id) == action;
active_actions.remove(id);
for (ActionsListener l : action_listeners) l.onContextActionDone(action);
startAction(id);
}
/**
* Remove all actions from the queue of a debug context.
* @param id - debug context ID.
*/
public void removeContextActions(String id) {
assert Protocol.isDispatchThread();
context_action_queue.remove(id);
context_action_timestamps.remove(id);
}
/**
* Get action queue size of a debug context.
* @param id - debug context ID.
* @return count of pending actions.
*/
public int getContextActionsCount(String id) {
assert Protocol.isDispatchThread();
LinkedList<TCFAction> list = context_action_queue.get(id);
int n = list == null ? 0 : list.size();
if (active_actions.get(id) != null) n++;
return n;
}
/**
* Add a listener that will be notified when an action execution is started or finished,
* or when an action result is posted.
* @param l - action listener.
*/
public void addActionsListener(ActionsListener l) {
action_listeners.add(l);
}
/**
* Remove an action listener that was registered with addActionsListener().
* @param l - action listener.
*/
public void removeActionsListener(ActionsListener l) {
action_listeners.remove(l);
}
/**
* Get context filer of the launch.
* By default, TCF debugger shows all remote debug contexts.
* Context filter is used to hide unwanted contexts to reduce UI clutter.
* @return context filter.
*/
public Set<String> getContextFilter() {
return context_filter;
}
}