/*******************************************************************************
* 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.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
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.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.tcf.internal.debug.launch.TCFUserDefPeer;
import org.eclipse.tcf.internal.debug.ui.Activator;
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.IPeer;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.ILocator;
// Cloned from TCFTargetTab
public class PeerListControl implements ISelectionProvider {
private Tree peer_tree;
private final PeerInfo peer_info = new PeerInfo();
private Display display;
private final ListenerList selection_listeners = new ListenerList(ListenerList.IDENTITY);
private String initial_peer_id;
public static class PeerInfo {
public String id;
public IPeer peer;
PeerInfo parent;
Map<String,String> attrs;
PeerInfo[] children;
boolean children_pending;
Throwable children_error;
IChannel channel;
ILocator locator;
LocatorListener listener;
Runnable item_update;
}
private class LocatorListener implements ILocator.LocatorListener {
private final PeerInfo parent;
LocatorListener(PeerInfo parent) {
this.parent = parent;
}
public void peerAdded(final IPeer peer) {
if (display == null) return;
final String id = peer.getID();
final HashMap<String,String> attrs = new HashMap<String,String>(peer.getAttributes());
display.asyncExec(new Runnable() {
public void run() {
if (parent.children_error != null) return;
PeerInfo[] arr = parent.children;
for (PeerInfo p : arr) assert !p.id.equals(id);
PeerInfo[] buf = new PeerInfo[arr.length + 1];
System.arraycopy(arr, 0, buf, 0, arr.length);
PeerInfo info = new PeerInfo();
info.parent = parent;
info.id = id;
info.attrs = attrs;
info.peer = peer;
buf[arr.length] = info;
parent.children = buf;
updateItems(parent);
}
});
}
public void peerChanged(final IPeer peer) {
if (display == null) return;
final String id = peer.getID();
final HashMap<String,String> attrs = new HashMap<String,String>(peer.getAttributes());
display.asyncExec(new Runnable() {
public void run() {
if (parent.children_error != null) return;
PeerInfo[] arr = parent.children;
for (int i = 0; i < arr.length; i++) {
if (arr[i].id.equals(id)) {
arr[i].attrs = attrs;
arr[i].peer = peer;
updateItems(parent);
}
}
}
});
}
public void peerRemoved(final String id) {
if (display == null) return;
display.asyncExec(new Runnable() {
public void run() {
if (parent.children_error != null) return;
PeerInfo[] arr = parent.children;
PeerInfo[] buf = new PeerInfo[arr.length - 1];
int j = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i].id.equals(id)) {
final PeerInfo info = arr[i];
Protocol.invokeLater(new Runnable() {
public void run() {
disconnectPeer(info);
}
});
}
else {
buf[j++] = arr[i];
}
}
parent.children = buf;
updateItems(parent);
}
});
}
public void peerHeartBeat(final String id) {
if (display == null) return;
display.asyncExec(new Runnable() {
public void run() {
if (parent.children_error != null) return;
PeerInfo[] arr = parent.children;
for (int i = 0; i < arr.length; i++) {
if (arr[i].id.equals(id)) {
if (arr[i].children_error != null) {
TreeItem item = findItem(arr[i]);
boolean visible = item != null;
while (visible && item != null) {
if (!item.getExpanded()) visible = false;
item = item.getParentItem();
}
if (visible) loadChildren(arr[i]);
}
break;
}
}
}
});
}
}
public PeerListControl(Composite parent) {
display = parent.getDisplay();
parent.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
handleDispose();
}
});
loadChildren(peer_info);
createPeerListArea(parent);
}
public void setInitialSelection(String id) {
if (id == null) return;
if (id.length() == 0) return;
PeerInfo info = findPeerInfo(id);
if (info != null) {
setSelection(new StructuredSelection(info));
fireSelectionChangedEvent();
onPeerSelected(info);
}
else {
String p = id;
for (;;) {
int i = p.lastIndexOf('/');
if (i < 0) break;
p = p.substring(0, i);
TreeItem item = findItem(p);
if (item != null) item.setExpanded(true);
}
initial_peer_id = id;
}
}
public Tree getTree() {
return peer_tree;
}
private void createPeerListArea(Composite parent) {
Font font = parent.getFont();
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
composite.setFont(font);
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true, 2, 1));
peer_tree = new Tree(composite, SWT.VIRTUAL | SWT.BORDER | SWT.SINGLE);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.minimumHeight = 150;
gd.minimumWidth = 470;
peer_tree.setLayoutData(gd);
for (int i = 0; i < 6; i++) {
TreeColumn column = new TreeColumn(peer_tree, SWT.LEAD, i);
column.setMoveable(true);
switch (i) {
case 0:
column.setText("Name");
column.setWidth(160);
break;
case 1:
column.setText("OS");
column.setWidth(100);
break;
case 2:
column.setText("User");
column.setWidth(100);
break;
case 3:
column.setText("Transport");
column.setWidth(60);
break;
case 4:
column.setText("Host");
column.setWidth(100);
break;
case 5:
column.setText("Port");
column.setWidth(40);
break;
}
}
peer_tree.setHeaderVisible(true);
peer_tree.setFont(font);
peer_tree.addListener(SWT.SetData, new Listener() {
@Override
public void handleEvent(Event event) {
TreeItem item = (TreeItem)event.item;
PeerInfo info = findPeerInfo(item);
if (info == null) {
updateItems(item.getParentItem(), false);
}
else {
fillItem(item, info);
onPeerListChanged();
}
}
});
peer_tree.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
TreeItem[] selections = peer_tree.getSelection();
if (selections.length == 0) return;
final PeerInfo info = findPeerInfo(selections[0]);
if (info == null) return;
new PeerPropsDialog(peer_tree.getShell(), getImage(info), info.attrs,
info.peer instanceof TCFUserDefPeer).open();
if (!(info.peer instanceof TCFUserDefPeer)) return;
Protocol.invokeLater(new Runnable() {
public void run() {
((TCFUserDefPeer)info.peer).updateAttributes(info.attrs);
TCFUserDefPeer.savePeers();
}
});
}
@Override
public void widgetSelected(SelectionEvent e) {
fireSelectionChangedEvent();
TreeItem[] selections = peer_tree.getSelection();
if (selections.length > 0) {
assert selections.length == 1;
PeerInfo info = findPeerInfo(selections[0]);
if (info != null) {
initial_peer_id = null;
onPeerSelected(info);
}
}
onPeerListChanged();
}
});
peer_tree.addTreeListener(new TreeListener() {
@Override
public void treeCollapsed(TreeEvent e) {
updateItems((TreeItem)e.item, false);
}
@Override
public void treeExpanded(TreeEvent e) {
updateItems((TreeItem)e.item, true);
}
});
}
private void handleDispose() {
Protocol.invokeAndWait(new Runnable() {
public void run() {
disconnectPeer(peer_info);
display = null;
}
});
}
private void disconnectPeer(final PeerInfo info) {
assert Protocol.isDispatchThread();
if (info.children != null) {
for (PeerInfo p : info.children) disconnectPeer(p);
}
if (info.listener != null) {
info.locator.removeListener(info.listener);
info.listener = null;
info.locator = null;
}
if (info.channel != null) {
info.channel.close();
}
}
private boolean canHaveChildren(PeerInfo parent) {
return parent == peer_info || parent.attrs.get(IPeer.ATTR_PROXY) != null;
}
private void loadChildren(final PeerInfo parent) {
assert Thread.currentThread() == display.getThread();
if (parent.children_pending) return;
assert parent.children == null;
parent.children_pending = true;
parent.children_error = null;
Protocol.invokeAndWait(new Runnable() {
public void run() {
assert parent.listener == null;
assert parent.channel == null;
if (!canHaveChildren(parent)) {
doneLoadChildren(parent, null, new PeerInfo[0]);
}
else if (parent == peer_info) {
peer_info.locator = Protocol.getLocator();
doneLoadChildren(parent, null, createLocatorListener(peer_info));
}
else {
final IChannel channel = parent.peer.openChannel();
parent.channel = channel;
parent.channel.addChannelListener(new IChannel.IChannelListener() {
boolean opened = false;
boolean closed = false;
public void congestionLevel(int level) {
}
public void onChannelClosed(final Throwable error) {
assert !closed;
if (parent.channel != channel) return;
if (!opened) {
doneLoadChildren(parent, error, null);
}
else {
if (display != null) {
display.asyncExec(new Runnable() {
public void run() {
if (parent.children_pending) return;
parent.children = null;
parent.children_error = error;
updateItems(parent);
}
});
}
}
closed = true;
parent.channel = null;
parent.locator = null;
parent.listener = null;
}
public void onChannelOpened() {
assert !opened;
assert !closed;
if (parent.channel != channel) return;
opened = true;
parent.locator = parent.channel.getRemoteService(ILocator.class);
if (parent.locator == null) {
parent.channel.terminate(new Exception("Service not supported: " + ILocator.NAME));
}
else {
doneLoadChildren(parent, null, createLocatorListener(parent));
}
}
});
}
}
});
}
private PeerInfo[] createLocatorListener(PeerInfo peer) {
assert Protocol.isDispatchThread();
Map<String,IPeer> map = peer.locator.getPeers();
PeerInfo[] buf = new PeerInfo[map.size()];
int n = 0;
for (IPeer p : map.values()) {
PeerInfo info = new PeerInfo();
info.parent = peer;
info.id = p.getID();
info.attrs = new HashMap<String,String>(p.getAttributes());
info.peer = p;
buf[n++] = info;
}
peer.listener = new LocatorListener(peer);
peer.locator.addListener(peer.listener);
return buf;
}
private void doneLoadChildren(final PeerInfo parent, final Throwable error, final PeerInfo[] children) {
assert Protocol.isDispatchThread();
assert error == null || children == null;
if (display == null) return;
display.asyncExec(new Runnable() {
public void run() {
assert parent.children_pending;
assert parent.children == null;
parent.children_pending = false;
parent.children = children;
parent.children_error = error;
updateItems(parent);
}
});
}
private ArrayList<PeerInfo> filterPeerList(PeerInfo parent, boolean expanded) {
ArrayList<PeerInfo> lst = new ArrayList<PeerInfo>();
HashMap<String,PeerInfo> local_agents = new HashMap<String,PeerInfo>();
HashSet<String> ids = new HashSet<String>();
for (PeerInfo p : parent.children) {
String id = p.attrs.get(IPeer.ATTR_AGENT_ID);
if (id == null) continue;
if (!"TCP".equals(p.attrs.get(IPeer.ATTR_TRANSPORT_NAME))) continue;
if (!"127.0.0.1".equals(p.attrs.get(IPeer.ATTR_IP_HOST))) continue;
if (isFiltered(p)) continue;
local_agents.put(id, p);
ids.add(p.id);
}
for (PeerInfo p : parent.children) {
PeerInfo i = local_agents.get(p.attrs.get(IPeer.ATTR_AGENT_ID));
if (i != null && i != p) continue;
if (isFiltered(p)) continue;
lst.add(p);
}
if (parent != peer_info && expanded) {
for (PeerInfo p : peer_info.children) {
if (p.peer instanceof TCFUserDefPeer && !ids.contains(p.id)) {
PeerInfo x = new PeerInfo();
x.parent = parent;
x.id = p.id;
x.attrs = p.attrs;
x.peer = p.peer;
ids.add(x.id);
lst.add(x);
}
}
}
return lst;
}
private boolean isFiltered(PeerInfo p) {
boolean filtered = false;
if (p != null && p.attrs != null) {
filtered |= p.attrs.get("ValueAdd") != null && ("1".equals(p.attrs.get("ValueAdd").trim()) || Boolean.parseBoolean(p.attrs.get("ValueAdd").trim())); //$NON-NLS-1$
filtered |= p.attrs.get(IPeer.ATTR_NAME) != null
&& p.attrs.get(IPeer.ATTR_NAME).endsWith("Command Server"); //$NON-NLS-1$
}
return filtered;
}
private void updateItems(TreeItem parent_item, boolean reload) {
final PeerInfo parent_info = findPeerInfo(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 PeerInfo parent) {
if (display == null) return;
assert Thread.currentThread() == display.getThread();
parent.item_update = new Runnable() {
public void run() {
if (display == null) return;
if (parent.item_update != this) return;
if (Thread.currentThread() != display.getThread()) {
display.asyncExec(this);
return;
}
parent.item_update = null;
TreeItem[] items = null;
boolean expanded = true;
if (parent.children == null || parent.children_error != null) {
if (parent == peer_info) {
peer_tree.setItemCount(1);
items = peer_tree.getItems();
}
else {
TreeItem item = findItem(parent);
if (item == null) return;
expanded = item.getExpanded();
item.setItemCount(1);
items = item.getItems();
}
assert items.length == 1;
if (parent.children_pending) {
items[0].setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
fillItem(items[0], "Connecting...");
}
else if (parent.children_error != null) {
String msg = TCFModel.getErrorMessage(parent.children_error, false);
items[0].setForeground(display.getSystemColor(SWT.COLOR_RED));
fillItem(items[0], msg);
}
else if (expanded) {
loadChildren(parent);
items[0].setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
fillItem(items[0], "Connecting...");
}
else {
Protocol.invokeAndWait(new Runnable() {
public void run() {
disconnectPeer(parent);
}
});
fillItem(items[0], "");
}
}
else {
ArrayList<PeerInfo> lst = null;
if (parent == peer_info) {
lst = filterPeerList(parent, expanded);
peer_tree.setItemCount(lst.size() > 0 ? lst.size() : 1);
items = peer_tree.getItems();
}
else {
TreeItem item = findItem(parent);
if (item == null) return;
expanded = item.getExpanded();
lst = filterPeerList(parent, expanded);
item.setItemCount(expanded && lst.size() > 0 ? lst.size() : 1);
items = item.getItems();
}
if (expanded && lst.size() > 0) {
assert items.length == lst.size();
for (int i = 0; i < items.length; i++) fillItem(items[i], lst.get(i));
}
else if (expanded) {
fillItem(items[0], "No peers");
}
else {
Protocol.invokeAndWait(new Runnable() {
public void run() {
disconnectPeer(parent);
}
});
fillItem(items[0], "");
}
}
onPeerListChanged();
if (initial_peer_id != null) {
setInitialSelection(initial_peer_id);
}
}
};
if (parent.children_pending) parent.item_update.run();
else Protocol.invokeLater(200, parent.item_update);
}
public TreeItem findItem(String path) {
assert Thread.currentThread() == display.getThread();
if (path == null) return null;
int z = path.lastIndexOf('/');
if (z < 0) {
int n = peer_tree.getItemCount();
for (int i = 0; i < n; i++) {
TreeItem x = peer_tree.getItem(i);
PeerInfo p = (PeerInfo)x.getData("TCFPeerInfo");
if (p != null && p.id.equals(path)) return x;
}
}
else {
TreeItem y = findItem(path.substring(0, z));
if (y == null) return null;
String id = path.substring(z + 1);
int n = y.getItemCount();
for (int i = 0; i < n; i++) {
TreeItem x = y.getItem(i);
PeerInfo p = (PeerInfo)x.getData("TCFPeerInfo");
if (p != null && p.id.equals(id)) return x;
}
}
return null;
}
public TreeItem findItem(PeerInfo info) {
if (info == null) return null;
assert info.parent != null;
if (info.parent == peer_info) {
int n = peer_tree.getItemCount();
for (int i = 0; i < n; i++) {
TreeItem x = peer_tree.getItem(i);
if (x.getData("TCFPeerInfo") == info) return x;
}
}
else {
TreeItem y = findItem(info.parent);
if (y == null) return null;
int n = y.getItemCount();
for (int i = 0; i < n; i++) {
TreeItem x = y.getItem(i);
if (x.getData("TCFPeerInfo") == info) return x;
}
}
return null;
}
public String getPath(PeerInfo info) {
if (info == peer_info) return "";
if (info.parent == peer_info) return info.id;
return getPath(info.parent) + "/" + info.id;
}
public PeerInfo findPeerInfo(String path) {
TreeItem i = findItem(path);
if (i == null) return null;
return (PeerInfo)i.getData("TCFPeerInfo");
}
private PeerInfo findPeerInfo(TreeItem item) {
assert Thread.currentThread() == display.getThread();
if (item == null) return peer_info;
return (PeerInfo)item.getData("TCFPeerInfo");
}
private void fillItem(TreeItem item, PeerInfo info) {
assert Thread.currentThread() == display.getThread();
Object data = item.getData("TCFPeerInfo");
if (data != null && data != info) item.removeAll();
item.setData("TCFPeerInfo", info);
String text[] = new String[6];
text[0] = info.attrs.get(IPeer.ATTR_NAME);
text[1] = info.attrs.get(IPeer.ATTR_OS_NAME);
text[2] = info.attrs.get(IPeer.ATTR_USER_NAME);
text[3] = info.attrs.get(IPeer.ATTR_TRANSPORT_NAME);
text[4] = info.attrs.get(IPeer.ATTR_IP_HOST);
text[5] = info.attrs.get(IPeer.ATTR_IP_PORT);
for (int i = 0; i < text.length; i++) {
if (text[i] == null) text[i] = "";
}
item.setText(text);
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 void fillItem(TreeItem item, String text) {
item.setText(text);
item.setData("TCFPeerInfo", null);
int n = peer_tree.getColumnCount();
for (int i = 1; i < n; i++) item.setText(i, "");
item.setImage((Image)null);
item.removeAll();
}
private Image getImage(PeerInfo info) {
return ImageCache.getImage(ImageCache.IMG_TARGET_TAB);
}
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selection_listeners.add(listener);
}
public ISelection getSelection() {
TreeItem[] items = peer_tree.getSelection();
PeerInfo[] peers = new PeerInfo[items.length];
int i = 0;
for (TreeItem item : items) {
peers[i++] = findPeerInfo(item);
}
return new StructuredSelection(peers);
}
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selection_listeners.remove(listener);
}
public void setSelection(ISelection selection) {
peer_tree.deselectAll();
if (selection instanceof IStructuredSelection) {
Object[] elements = ((IStructuredSelection) selection).toArray();
for (Object object : elements) {
if (object instanceof PeerInfo) {
TreeItem item = findItem((PeerInfo) object);
if (item != null) {
peer_tree.select(item);
}
}
}
}
}
private void fireSelectionChangedEvent() {
SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
Object[] listeners = selection_listeners.getListeners();
for (Object listener : listeners) {
try {
((ISelectionChangedListener) listener).selectionChanged(event);
}
catch (Exception e) {
Activator.log(e);
}
}
}
protected void onPeerListChanged() {
}
protected void onPeerSelected(PeerInfo info) {
}
}