/*******************************************************************************
* Copyright (c) 2004, 2011 QNX Software Systems 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:
* QNX Software Systems - Initial API and implementation
* Wind River Systems - Adapted to TCF
*******************************************************************************/
package org.eclipse.tm.internal.tcf.cdt.ui.breakpoints;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.tm.internal.tcf.cdt.ui.ImageCache;
import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch;
import org.eclipse.tm.internal.tcf.debug.ui.model.TCFChildren;
import org.eclipse.tm.internal.tcf.debug.ui.model.TCFModel;
import org.eclipse.tm.internal.tcf.debug.ui.model.TCFModelManager;
import org.eclipse.tm.internal.tcf.debug.ui.model.TCFNode;
import org.eclipse.tm.internal.tcf.debug.ui.model.TCFNodeExecContext;
import org.eclipse.tm.tcf.services.IRunControl;
import org.eclipse.tm.tcf.util.TCFDataCache;
import org.eclipse.tm.tcf.util.TCFTask;
public class TCFThreadFilterEditor {
private static class Context {
private final String fName;
private final String fId;
private final String fParentId;
private final boolean fIsContainer;
private final String fScopeId;
private final String fSessionId;
Context(IRunControl.RunControlContext ctx, Context parent) {
this(ctx, parent.fSessionId);
}
Context(IRunControl.RunControlContext ctx, String sessionId) {
String name = ctx.getName() != null ? ctx.getName() : ctx.getID();
fName = name;
fSessionId = sessionId;
fScopeId = sessionId != null ? sessionId + '/' + ctx.getID() : ctx.getID();
fId = ctx.getID();
fParentId = ctx.getParentID();
fIsContainer = ctx.isContainer();
}
}
public class CheckHandler implements ICheckStateListener {
public void checkStateChanged(CheckStateChangedEvent event) {
Object element = event.getElement();
boolean checked = event.getChecked();
if (checked) {
getThreadViewer().expandToLevel(element, 1);
}
if (element instanceof Context) {
Context ctx = (Context) element;
checkContext(ctx, checked);
updateParentCheckState(ctx);
} else if (element instanceof ILaunch) {
checkLaunch((ILaunch) element, checked);
}
}
private void checkLaunch(ILaunch launch, boolean checked) {
getThreadViewer().setChecked(launch, checked);
getThreadViewer().setGrayed(launch, false);
Context[] threads = syncGetContainers((TCFLaunch) launch);
for (int i = 0; i < threads.length; i++) {
checkContext(threads[i], checked);
}
}
/**
* Check or uncheck a context in the tree viewer. When a container
* is checked, attempt to check all of the containers threads by
* default. When a container is unchecked, uncheck all its threads.
*/
private void checkContext(Context ctx, boolean checked) {
if (ctx.fIsContainer) {
Context[] threads = syncGetThreads(ctx);
for (int i = 0; i < threads.length; i++) {
checkContext(threads[i], checked);
}
}
checkThread(ctx, checked);
}
/**
* Check or uncheck a thread.
*/
private void checkThread(Context thread, boolean checked) {
getThreadViewer().setChecked(thread, checked);
getThreadViewer().setGrayed(thread, false);
}
private void updateParentCheckState(Context thread) {
Context[] threads;
Object parent = getContainer(thread);
if (parent == null) {
parent = getLaunch(thread);
if (parent == null) return;
threads = syncGetContainers((TCFLaunch) parent);
} else {
threads = syncGetThreads((Context) parent);
}
int checkedNumber = 0;
int grayedNumber = 0;
for (int i = 0; i < threads.length; i++) {
if (getThreadViewer().getGrayed(threads[i])) {
++grayedNumber;
} else if (getThreadViewer().getChecked(threads[i])) {
++checkedNumber;
}
}
if (checkedNumber + grayedNumber == 0) {
getThreadViewer().setChecked(parent, false);
getThreadViewer().setGrayed(parent, false);
} else if (checkedNumber == threads.length) {
getThreadViewer().setChecked(parent, true);
getThreadViewer().setGrayed(parent, false);
} else {
getThreadViewer().setGrayChecked(parent, true);
}
if (parent instanceof Context) {
updateParentCheckState((Context) parent);
}
}
}
public class ThreadFilterContentProvider implements ITreeContentProvider {
public Object[] getChildren(Object parent) {
if (parent instanceof Context) {
return syncGetThreads((Context) parent);
}
if (parent instanceof ILaunch) {
return syncGetContainers((TCFLaunch) parent);
}
if (parent instanceof ILaunchManager) {
return getLaunches();
}
return new Object[0];
}
public Object getParent(Object element) {
if (element instanceof Context) {
Context ctx = (Context) element;
if (ctx.fParentId == null) {
return DebugPlugin.getDefault().getLaunchManager();
} else {
return getContainer(ctx);
}
}
return null;
}
public boolean hasChildren(Object element) {
return getChildren(element).length > 0;
}
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
public class ThreadFilterLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
if (element instanceof Context) {
Context ctx = (Context) element;
if (ctx.fIsContainer) {
return DebugUITools.getImage(IDebugUIConstants.IMG_OBJS_DEBUG_TARGET);
} else {
return DebugUITools.getImage(IDebugUIConstants.IMG_OBJS_THREAD_RUNNING);
}
}
if (element instanceof ILaunch) {
ImageDescriptor desc = DebugUITools.getDefaultImageDescriptor(element);
if (desc != null) return ImageCache.getImage(desc);
}
return null;
}
@Override
public String getText(Object element) {
if (element instanceof Context) {
Context ctx = (Context) element;
return ctx.fName;
}
if (element instanceof ILaunch) {
ILaunchConfiguration config = ((ILaunch) element).getLaunchConfiguration();
if (config != null) return config.getName();
}
return "?";
}
}
private TCFBreakpointThreadFilterPage fPage;
private CheckboxTreeViewer fThreadViewer;
private final ThreadFilterContentProvider fContentProvider;
private final CheckHandler fCheckHandler;
private final List<Context> fContexts = new ArrayList<Context>();
private final Map<TCFLaunch, Context[]> fContainersPerLaunch = new HashMap<TCFLaunch, Context[]>();
private final Map<Context, Context[]> fContextsPerContainer = new HashMap<Context, Context[]>();
public TCFThreadFilterEditor(Composite parent, TCFBreakpointThreadFilterPage page) {
fPage = page;
fContentProvider = new ThreadFilterContentProvider();
fCheckHandler = new CheckHandler();
createThreadViewer(parent);
}
protected TCFBreakpointThreadFilterPage getPage() {
return fPage;
}
private void createThreadViewer(Composite parent) {
Label label = new Label(parent, SWT.NONE);
label.setText("Restrict to Selected Contexts:"); //$NON-NLS-1$
label.setFont(parent.getFont());
label.setLayoutData(new GridData());
GridData data = new GridData(GridData.FILL_BOTH);
data.heightHint = 100;
fThreadViewer = new CheckboxTreeViewer(parent, SWT.BORDER);
fThreadViewer.addCheckStateListener(fCheckHandler);
fThreadViewer.getTree().setLayoutData(data);
fThreadViewer.getTree().setFont(parent.getFont());
fThreadViewer.setContentProvider(fContentProvider);
fThreadViewer.setLabelProvider(new ThreadFilterLabelProvider());
fThreadViewer.setInput(DebugPlugin.getDefault().getLaunchManager());
setInitialCheckedState();
}
protected ILaunch[] getLaunches() {
Object input = fThreadViewer.getInput();
if (!(input instanceof ILaunchManager)) {
return new ILaunch[0];
}
List<ILaunch> tcfLaunches = new ArrayList<ILaunch>();
ILaunch[] launches = ((ILaunchManager) input).getLaunches();
for (int i = 0; i < launches.length; i++) {
ILaunch launch = launches[i];
if (launch instanceof TCFLaunch && !launch.isTerminated()) {
tcfLaunches.add(launch);
}
}
return tcfLaunches.toArray(new ILaunch[tcfLaunches.size()]);
}
/**
* Returns the root contexts that appear in the tree
*/
protected Context[] getRootContexts() {
Object input = fThreadViewer.getInput();
if (!(input instanceof ILaunchManager)) {
return new Context[0];
}
List<Object> targets = new ArrayList<Object>();
ILaunch[] launches = ((ILaunchManager) input).getLaunches();
for (int i = 0; i < launches.length; i++) {
ILaunch launch = launches[i];
if (launch instanceof TCFLaunch && !launch.isTerminated()) {
Context[] targetArray = syncGetContainers((TCFLaunch) launch);
targets.addAll(Arrays.asList(targetArray));
}
}
return targets.toArray(new Context[targets.size()]);
}
protected final CheckboxTreeViewer getThreadViewer() {
return fThreadViewer;
}
/**
* Sets the initial checked state of the tree viewer. The initial state
* should reflect the current state of the breakpoint. If the breakpoint has
* a thread filter in a given thread, that thread should be checked.
*/
protected void setInitialCheckedState() {
TCFBreakpointScopeExtension filterExtension = fPage.getFilterExtension();
if (filterExtension == null) {
return;
}
String[] ctxIds = filterExtension.getThreadFilters();
// expand all to realize tree items
getThreadViewer().expandAll();
if (ctxIds == null) {
ILaunch[] launches = getLaunches();
for (ILaunch launch : launches) {
fCheckHandler.checkLaunch(launch, true);
}
} else if (ctxIds.length != 0) {
for (int i = 0; i < ctxIds.length; i++) {
String id = ctxIds[i];
Context ctx = getContext(id);
if (ctx != null) {
fCheckHandler.checkContext(ctx, true);
fCheckHandler.updateParentCheckState(ctx);
} else if (id.indexOf('/') < 0) {
for (Context context : fContexts) {
if (id.equals(context.fId)) {
fCheckHandler.checkContext(context, true);
fCheckHandler.updateParentCheckState(context);
}
}
}
}
}
// expand checked items only
getThreadViewer().setExpandedElements(getThreadViewer().getCheckedElements());
}
private Context getContainer(Context child) {
String parentId = child.fSessionId != null ? child.fSessionId + '/' + child.fParentId : child.fParentId;
return getContext(parentId);
}
private Context getContext(String id) {
for (Context ctx : fContexts) {
if (ctx.fScopeId.equals(id))
return ctx;
}
return null;
}
protected void doStore() {
CheckboxTreeViewer viewer = getThreadViewer();
Object[] elements = viewer.getCheckedElements();
String[] threadIds;
List<String> checkedIds = new ArrayList<String>();
for (int i = 0; i < elements.length; ++i) {
if (elements[i] instanceof Context) {
Context ctx = (Context) elements[i];
if (!viewer.getGrayed(ctx)) {
checkedIds.add(ctx.fScopeId);
}
}
}
if (checkedIds.size() == fContexts.size()) {
threadIds = null;
} else {
threadIds = checkedIds.toArray(new String[checkedIds.size()]);
}
TCFBreakpointScopeExtension filterExtension = fPage.getFilterExtension();
if (filterExtension == null) {
return;
}
filterExtension.setThreadFilter(threadIds);
DebugPlugin.getDefault().getBreakpointManager().fireBreakpointChanged(fPage.getBreakpoint());
}
private Context[] syncGetContainers(final TCFLaunch launch) {
Context[] result = fContainersPerLaunch.get(launch);
if (result != null) {
return result;
}
final String launchCfgName = launch.getLaunchConfiguration().getName();
result = new TCFTask<Context[]>(launch.getChannel()) {
public void run() {
List<Context> containers = new ArrayList<Context>();
TCFModel model = TCFModelManager.getModelManager().getModel(launch);
TCFChildren children = model.getRootNode().getChildren();
if (!children.validate(this)) return;
Map<String, TCFNode> childMap = children.getData();
for (TCFNode node : childMap.values()) {
if (node instanceof TCFNodeExecContext) {
TCFNodeExecContext exeCtx = (TCFNodeExecContext) node;
TCFDataCache<IRunControl.RunControlContext> runCtxCache = exeCtx.getRunContext();
if (!runCtxCache.validate(this)) return;
IRunControl.RunControlContext runCtx = runCtxCache.getData();
containers.add(new Context(runCtx, launchCfgName));
}
}
done(containers.toArray(new Context[containers.size()]));
}
}.getE();
fContexts.addAll(Arrays.asList(result));
fContainersPerLaunch.put(launch, result);
return result;
}
private Context[] syncGetThreads(final Context container) {
Context[] result = fContextsPerContainer.get(container);
if (result != null) {
return result;
}
final TCFLaunch launch = getLaunch(container);
result = new TCFTask<Context[]>(launch.getChannel()) {
public void run() {
List<Context> contexts = new ArrayList<Context>();
TCFModel model = TCFModelManager.getModelManager().getModel(launch);
TCFChildren children = ((TCFNodeExecContext) model.getNode(container.fId)).getChildren();
if (!children.validate(this)) return;
Collection<TCFNode> childNodes = children.getData().values();
TCFNode[] nodes = childNodes.toArray(new TCFNode[childNodes.size()]);
Arrays.sort(nodes);
for (TCFNode node : nodes) {
if (node instanceof TCFNodeExecContext) {
TCFNodeExecContext exeCtx = (TCFNodeExecContext) node;
TCFDataCache<IRunControl.RunControlContext> runCtxCache = exeCtx.getRunContext();
if (!runCtxCache.validate(this)) return;
IRunControl.RunControlContext runCtx = runCtxCache.getData();
contexts.add(new Context(runCtx, container));
}
}
done(contexts.toArray(new Context[contexts.size()]));
}
}.getE();
fContextsPerContainer.put(container, result);
fContexts.addAll(Arrays.asList(result));
return result;
}
private TCFLaunch getLaunch(Context container) {
Context parent = getContainer(container);
while (parent != null) {
container = parent;
parent = getContainer(container);
}
for (TCFLaunch launch : fContainersPerLaunch.keySet()) {
Context[] containers = fContainersPerLaunch.get(launch);
for (Context context : containers) {
if (context.fScopeId.equals(container.fScopeId)) {
return launch;
}
}
}
return null;
}
}