/******************************************************************************* * Copyright (c) 2011, 2013 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.ui.launch; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.tcf.internal.debug.ui.ImageCache; import org.eclipse.tcf.internal.debug.ui.model.TCFModel; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IChannel.IChannelListener; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.services.IProcesses; import org.eclipse.tcf.services.IProcesses.ProcessContext; import org.eclipse.tcf.services.IRunControl; import org.eclipse.tcf.services.IRunControl.RunControlContext; import org.eclipse.tcf.services.ISysMonitor; import org.eclipse.tcf.services.ISysMonitor.SysMonitorContext; import org.eclipse.tcf.util.TCFTask; public class ContextListControl { private final class ProcessListener implements IProcesses.ProcessesListener { public void exited(final String process_id, int exit_code) { onContextRemoved(process_id); } } private final class ContextListener implements IRunControl.RunControlListener { @Override public void contextAdded(RunControlContext[] contexts) { for (RunControlContext ctx : contexts) { onContextAdded(ctx.getParentID()); } } @Override public void contextChanged(RunControlContext[] contexts) { } @Override public void contextRemoved(String[] context_ids) { for (String id : context_ids) { onContextRemoved(id); } } @Override public void contextSuspended(String context, String pc, String reason, Map<String, Object> params) { } @Override public void contextResumed(String context) { } @Override public void containerSuspended(String context, String pc, String reason, Map<String, Object> params, String[] suspended_ids) { } @Override public void containerResumed(String[] context_ids) { } @Override public void contextException(String context, String msg) { } } static class ContextInfo { String name; String id; Object additional_info; boolean is_attached; boolean is_container; boolean has_state; long pid; String[] cmd_line; ContextInfo[] children; Throwable children_error; boolean children_pending; boolean children_reload; ContextInfo parent; } private Composite composite; private Tree ctx_tree; private Display display; private IChannel channel; private String channel_state; private final ContextInfo root_info = new ContextInfo(); private final ProcessListener prs_listener = new ProcessListener(); private final ContextListener ctx_listener = new ContextListener(); private final boolean processes; private String initial_selection; public ContextListControl(Composite parent, boolean processes) { this.processes = processes; display = parent.getDisplay(); parent.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { Protocol.invokeAndWait(new Runnable() { public void run() { disconnectPeer(); display = null; } }); } }); createContextListArea(parent); } public void setInput(final IPeer peer) { assert Thread.currentThread() == display.getThread(); channel = new TCFTask<IChannel>() { public void run() { disconnectPeer(); done(connectPeer(peer)); } }.getE(); root_info.children = null; root_info.children_error = null; root_info.children_pending = false; root_info.children_reload = false; loadChildren(root_info); } public Control getControl() { return composite; } public Tree getTree() { return ctx_tree; } public ContextInfo getSelection() { if (ctx_tree != null) { initial_selection = null; TreeItem[] items = ctx_tree.getSelection(); if (items.length > 0) { ContextInfo info = findInfo(items[0]); return info; } } return null; } private void createContextListArea(Composite parent) { Font font = parent.getFont(); composite = new Composite(parent, SWT.NONE); composite.setFont(font); GridLayout layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); ctx_tree = new Tree(composite, SWT.VIRTUAL | SWT.BORDER | SWT.SINGLE); GridData gd = new GridData(GridData.FILL_BOTH); gd.minimumHeight = 150; gd.minimumWidth = 470; ctx_tree.setLayoutData(gd); ctx_tree.setFont(font); ctx_tree.addListener(SWT.SetData, new Listener() { public void handleEvent(Event event) { TreeItem item = (TreeItem)event.item; ContextInfo info = findInfo(item); if (info == null) { updateItems(item.getParentItem(), false); } else { fillItem(item, info); } } }); } protected void disconnectPeer() { if (channel != null && channel.getState() != IChannel.STATE_CLOSED) { channel.close(); } } protected IChannel connectPeer(IPeer peer) { if (peer == null) return null; final IChannel channel = peer.openChannel(); channel.addChannelListener(new IChannelListener() { public void congestionLevel(int level) { } public void onChannelClosed(final Throwable error) { if (display != null) { display.asyncExec(new Runnable() { public void run() { if (ContextListControl.this.channel != channel) return; root_info.children_error = error; loadChildren(root_info); } }); } } public void onChannelOpened() { if (processes) { IProcesses service = channel.getRemoteService(IProcesses.class); if (service != null) service.addListener(prs_listener); } else { IRunControl service = channel.getRemoteService(IRunControl.class); if (service != null) service.addListener(ctx_listener); } if (display != null) { display.asyncExec(new Runnable() { public void run() { if (ContextListControl.this.channel != channel) return; loadChildren(root_info); updateItems(root_info); } }); } } }); return channel; } private void updateItems(TreeItem parent_item, boolean reload) { final ContextInfo parent_info = findInfo(parent_item); if (parent_info == null) { parent_item.setText("Invalid"); } else { if (reload && parent_info.children_error != null) { loadChildren(parent_info); } display.asyncExec(new Runnable() { public void run() { updateItems(parent_info); } }); } } private void updateItems(final ContextInfo parent) { if (display == null) return; assert Thread.currentThread() == display.getThread(); TreeItem[] items = null; boolean expanded = true; if (parent.children == null || parent.children_error != null) { if (parent == root_info) { ctx_tree.deselectAll(); ctx_tree.setItemCount(1); items = ctx_tree.getItems(); } else { TreeItem item = findItem(parent); if (item == null) return; expanded = item.getExpanded(); item.setItemCount(1); items = item.getItems(); } assert items.length == 1; items[0].removeAll(); items[0].setImage((Image)null); items[0].setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); if (parent.children_pending) { items[0].setText("Pending..."); } else if (parent.children_error != null) { String msg = TCFModel.getErrorMessage(parent.children_error, false); items[0].setForeground(display.getSystemColor(SWT.COLOR_RED)); items[0].setText(msg); } else if (channel_state != null) { items[0].setText(channel_state); } else if (expanded) { loadChildren(parent); items[0].setText("Pending..."); } else { items[0].setText(""); } } else { ContextInfo[] arr = parent.children; if (parent == root_info) { ctx_tree.setItemCount(arr.length); items = ctx_tree.getItems(); } else { TreeItem item = findItem(parent); if (item == null) return; expanded = item.getExpanded(); item.setItemCount(expanded ? arr.length : 1); items = item.getItems(); } if (expanded) { assert items.length == arr.length; for (int i = 0; i < items.length; i++) fillItem(items[i], arr[i]); // auto-expand single children if (items.length == 1 && !items[0].getExpanded()) { items[0].setExpanded(true); } } else { items[0].setText(""); } } if (initial_selection != null) { setInitialSelection(initial_selection); } } private void loadChildren(final ContextInfo parent) { assert Thread.currentThread() == display.getThread(); final IChannel channel = this.channel; parent.children_reload = true; if (parent.children_pending) return; parent.children_reload = false; parent.children_pending = true; Protocol.invokeLater(new Runnable() { public void run() { if (channel == null || channel.getState() != IChannel.STATE_OPEN) { doneLoadChildren(channel, parent, null, new ContextInfo[0]); } else if (processes) { loadProcessChildren(channel, parent); } else { loadContextChildren(channel, parent); } } }); } private void loadProcessChildren(final IChannel channel, final ContextInfo parent) { final IProcesses service = channel.getRemoteService(IProcesses.class); final ISysMonitor sysmon = channel.getRemoteService(ISysMonitor.class); if (service == null) { doneLoadChildren(channel, parent, new Exception("Peer does not support Processes service"), null); } else if (!canHaveChildren(parent)) { doneLoadChildren(channel, parent, null, new ContextInfo[0]); } else { service.getChildren(parent.id, false, new IProcesses.DoneGetChildren() { public void doneGetChildren(IToken token, Exception error, String[] context_ids) { if (error != null) { doneLoadChildren(channel, parent, error, null); } else if (context_ids == null || context_ids.length == 0) { doneLoadChildren(channel, parent, null, new ContextInfo[0]); } else { final List<ContextInfo> infos = new ArrayList<ContextInfo>(context_ids.length); final Set<IToken> pending = new HashSet<IToken>(); for (final String id : context_ids) { final ContextInfo info = new ContextInfo(); info.parent = parent; info.id = id; pending.add(service.getContext(id, new IProcesses.DoneGetContext() { public void doneGetContext(IToken token, Exception error, ProcessContext context) { if (context != null) { info.name = context.getName(); info.is_attached = context.isAttached(); infos.add(info); } pending.remove(token); if (!pending.isEmpty()) return; doneLoadChildren(channel, parent, null, infos.toArray(new ContextInfo[infos.size()])); } })); if (sysmon != null) { pending.add(sysmon.getContext(id, new ISysMonitor.DoneGetContext() { @Override public void doneGetContext(IToken token, Exception error, SysMonitorContext context) { if (context != null) { info.pid = context.getPID(); } pending.remove(token); if (!pending.isEmpty()) return; doneLoadChildren(channel, parent, null, infos.toArray(new ContextInfo[infos.size()])); } })); pending.add(sysmon.getCommandLine(id, new ISysMonitor.DoneGetCommandLine() { @Override public void doneGetCommandLine(IToken token, Exception error, String[] cmd_line) { if (cmd_line != null) { info.cmd_line = cmd_line; } pending.remove(token); if (!pending.isEmpty()) return; doneLoadChildren(channel, parent, null, infos.toArray(new ContextInfo[infos.size()])); } })); } } } } }); } } private void loadContextChildren(final IChannel channel, final ContextInfo parent) { final IRunControl service = channel.getRemoteService(IRunControl.class); if (service == null) { doneLoadChildren(channel, parent, new Exception("Peer does not support Run Control service"), null); } else if (!canHaveChildren(parent)) { doneLoadChildren(channel, parent, null, new ContextInfo[0]); } else { service.getChildren(parent.id, new IRunControl.DoneGetChildren() { public void doneGetChildren(IToken token, Exception error, String[] context_ids) { if (error != null) { doneLoadChildren(channel, parent, error, null); } else if (context_ids != null && context_ids.length > 0) { final List<ContextInfo> infos = new ArrayList<ContextInfo>(context_ids.length); final Set<IToken> pending = new HashSet<IToken>(); for (String id : context_ids) { pending.add(service.getContext(id, new IRunControl.DoneGetContext() { public void doneGetContext(IToken token, Exception error, RunControlContext context) { if (context != null) { ContextInfo info = new ContextInfo(); info.parent = parent; info.id = context.getID(); info.name = context.getName(); info.additional_info = context.getProperties().get("AdditionalInfo"); info.is_container = context.isContainer(); info.has_state = context.hasState(); info.is_attached = true; infos.add(info); } pending.remove(token); if (!pending.isEmpty()) return; doneLoadChildren(channel, parent, null, infos.toArray(new ContextInfo[infos.size()])); } })); } } else { doneLoadChildren(channel, parent, null, new ContextInfo[0]); } } }); } } private void doneLoadChildren(final IChannel channel, final ContextInfo parent, final Throwable error, final ContextInfo[] children) { assert Protocol.isDispatchThread(); assert error == null || children == null; if (display == null) return; final String state = getChannelState(channel); display.asyncExec(new Runnable() { public void run() { if (ContextListControl.this.channel != channel) return; assert parent.children_pending; parent.children_pending = false; if (state == null) { parent.children = children; parent.children_error = error; if (parent.children_reload) { loadChildren(parent); } } else { parent.children = null; parent.children_reload = false; } channel_state = state; updateItems(parent); } }); } private String getChannelState(IChannel channel) { if (channel == null) return "Not connected"; switch (channel.getState()) { case IChannel.STATE_OPENING: return "Connecting..."; case IChannel.STATE_CLOSED: return "Disconnected"; } return null; } private void onContextRemoved(final String id) { if (display == null) return; display.asyncExec(new Runnable() { public void run() { ContextInfo info = findInfo(root_info, id); if (info != null && info.parent != null) loadChildren(info.parent); } }); } private void onContextAdded(final String parent_id) { if (display == null) return; display.asyncExec(new Runnable() { public void run() { ContextInfo info = findInfo(root_info, parent_id); if (info != null) loadChildren(info); } }); } public String getFullName(ContextInfo info) { if (info == null) return null; String name = info.name; if (name == null) name = info.id; if (info.parent == root_info) return "/" + name; if (info.parent == null) return null; String path = getFullName(info.parent); if (path == null) return null; return path + '/' + name; } private ContextInfo findInfoByFullName(ContextInfo parent, String name, boolean expand) { if (name == null) return null; if (name.startsWith("/")) return findInfoByFullName(root_info, name.substring(1), expand); if (parent.children_pending) return null; String head = name; String tail = null; int i = name.indexOf('/'); if (i >= 0) { head = name.substring(0, i); tail = name.substring(i + 1); } ContextInfo[] children = parent.children; if (children != null) { for (ContextInfo info : children) { if (head.equals(info.name)) { if (tail == null) return info; if (expand) { TreeItem item = findItem(info); if (item != null) item.setExpanded(true); } return findInfoByFullName(info, tail, expand); } } } else if (expand) { loadChildren(parent); } return null; } public ContextInfo findInfo(TreeItem item) { assert Thread.currentThread() == display.getThread(); if (item == null) return root_info; return (ContextInfo)item.getData("TCFContextInfo"); } public ContextInfo findInfo(ContextInfo parent, String id) { assert Thread.currentThread() == display.getThread(); if (id == null) return root_info; if (id.equals(parent.id)) return parent; ContextInfo[] children = parent.children; if (children != null) { for (ContextInfo info : children) { ContextInfo found = findInfo(info, id); if (found != null) return found; } } return null; } private TreeItem findItem(ContextInfo info) { assert Thread.currentThread() == display.getThread(); if (info == null) return null; assert info.parent != null; if (info.parent == root_info) { TreeItem[] items = ctx_tree.getItems(); for (TreeItem item : items) { if (item.getData("TCFContextInfo") == info) return item; } return null; } TreeItem parent = findItem(info.parent); if (parent == null) return null; TreeItem[] items = parent.getItems(); for (TreeItem item : items) { if (item.getData("TCFContextInfo") == info) return item; } return null; } private void fillItem(TreeItem item, ContextInfo info) { assert Thread.currentThread() == display.getThread(); Object data = item.getData("TCFContextInfo"); if (data != null && data != info) item.removeAll(); item.setData("TCFContextInfo", info); StringBuffer bf = new StringBuffer(); if (info.pid > 0) { bf.append(info.pid); bf.append(": "); } if (info.cmd_line != null && info.cmd_line.length > 0) { for (String s : info.cmd_line) { bf.append(' '); bf.append(s); } } else { if (info.name != null) { bf.append(info.name); } else { bf.append(info.id); } if (info.additional_info != null) { bf.append(info.additional_info.toString()); } } item.setText(bf.toString()); item.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); item.setImage(getImage(info)); if (!canHaveChildren(info)) item.setItemCount(0); else if (info.children == null || info.children_error != null) item.setItemCount(1); else item.setItemCount(info.children.length); } private boolean canHaveChildren(ContextInfo info) { return !info.has_state && (info.is_container || info == root_info); } private Image getImage(ContextInfo info) { return ImageCache.getImage(info.has_state ? ImageCache.IMG_THREAD_UNKNOWN_STATE : ImageCache.IMG_PROCESS_RUNNING); } public void setInitialSelection(String full_name) { if (full_name == null) return; if (full_name.length() == 0) return; initial_selection = full_name; ContextInfo info = findInfoByFullName(null, full_name, true); if (info != null) { TreeItem item = findItem(info); if (item != null) { ctx_tree.setSelection(item); initial_selection = null; } } } }