blob: 4d972944baeffa35dfb886d41ea7c2e8600d9ae0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.remote.internal.ui;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.IDeferredWorkbenchAdapter;
import org.eclipse.ui.progress.IElementCollector;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* A remote content manager that merges content into a tree rather then replacing its children with a
* "pending" node, and then the real children when they are available. This avoids collapsing the viewer when
* a refresh is performed. This implementation is currently tied to the <code>RemoteTreeViewer</code>.
*
* @since 3.1
*/
public class RemoteTreeContentManager {
private final RemoteTreeViewer fViewer;
private IWorkbenchSiteProgressService progressService;
/**
* Job to fetch children
*/
private final Job fFetchJob = new FetchJob();
/**
* Queue of parents to fetch children for, and associated element collectors and deferred adapters.
*/
private final List<Object> fElementQueue = new ArrayList<Object>();
private final List<IElementCollector> fCollectors = new ArrayList<IElementCollector>();
private final List<IDeferredWorkbenchAdapter> fAdapaters = new ArrayList<IDeferredWorkbenchAdapter>();
/**
* Fetching children is done in a single background job. This makes fetching single threaded/serial per
* view.
*/
private class FetchJob extends Job {
public FetchJob() {
super("FetchJob"); //$NON-NLS-1$
setSystem(true);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
while (!fElementQueue.isEmpty() && !monitor.isCanceled()) {
Object element = null;
IElementCollector collector = null;
IDeferredWorkbenchAdapter adapter = null;
synchronized (fElementQueue) {
// could have been cancelled after entering the while loop
if (fElementQueue.isEmpty()) {
return Status.CANCEL_STATUS;
}
element = fElementQueue.remove(0);
collector = fCollectors.remove(0);
adapter = fAdapaters.remove(0);
}
adapter.fetchDeferredChildren(element, collector, monitor);
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
}
/**
* Element collector
*/
private class Collector implements IElementCollector {
// number of children added to the tree
int offset = 0;
Object fParent;
public Collector(Object parent) {
fParent = parent;
}
@Override
public void add(Object element, IProgressMonitor monitor) {
add(new Object[] { element }, monitor);
}
@Override
public void add(Object[] elements, IProgressMonitor monitor) {
Object[] filtered = fViewer.filter(elements);
if (fViewer.getComparator() != null) {
fViewer.getComparator().sort(fViewer, filtered);
}
if (filtered.length > 0) {
replaceChildren(fParent, filtered, offset, monitor);
offset = offset + filtered.length;
}
}
@Override
public void done() {
prune(fParent, offset);
}
}
/**
* Contructs a new content manager.
*
* @param provider
* content provider
* @param viewer
* viewer
* @param site
* part site
*/
public RemoteTreeContentManager(ITreeContentProvider provider, RemoteTreeViewer viewer, IWorkbenchPartSite site) {
fViewer = viewer;
if (site != null) {
Object siteService = site.getAdapter(IWorkbenchSiteProgressService.class);
if (siteService != null) {
progressService = (IWorkbenchSiteProgressService) siteService;
}
}
}
/**
* Create the element collector for the receiver.
*
* @param parent
* The parent object being filled in,
* @param placeholder
* The adapter that will be used to indicate that results are pending, possibly <code>null</code>
* @return IElementCollector
*/
protected IElementCollector createElementCollector(Object parent, PendingUpdateAdapter placeholder) {
return new Collector(parent);
}
/**
* Returns the child elements of the given element, or in the case of a deferred element, returns a
* placeholder. If a deferred element is used, a job is created to fetch the children in the background.
*
* @param parent
* The parent object.
* @return Object[] or <code>null</code> if parent is not an instance of IDeferredWorkbenchAdapter.
*/
public Object[] getChildren(final Object parent) {
IDeferredWorkbenchAdapter element = getAdapter(parent);
if (element == null) {
return null;
}
Object[] currentChildren = fViewer.getCurrentChildren(parent);
PendingUpdateAdapter placeholder = null;
if (currentChildren == null || currentChildren.length == 0) {
placeholder = new PendingUpdateAdapter();
}
startFetchingDeferredChildren(parent, element, placeholder);
if (placeholder == null) {
return currentChildren;
}
return new Object[] { placeholder };
}
/**
* Create a UIJob to replace the children of the parent in the tree viewer.
*
* @param parent
* the parent for which children are to be replaced
* @param children
* the replacement children
* @param offset
* the offset at which to start replacing children
* @param monitor
* progress monitor
*/
protected void replaceChildren(final Object parent, final Object[] children, final int offset, IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return;
}
WorkbenchJob updateJob = new WorkbenchJob("IncrementalDeferredTreeContentManager") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor updateMonitor) {
// Cancel the job if the tree viewer got closed
if (fViewer.getControl().isDisposed()) {
return Status.CANCEL_STATUS;
}
fViewer.replace(parent, children, offset);
return Status.OK_STATUS;
}
};
updateJob.setSystem(true);
updateJob.setPriority(Job.INTERACTIVE);
updateJob.schedule();
}
/**
* Create a UIJob to prune the children of the parent in the tree viewer, starting at the given offset.
*
* @param parent
* the parent for which children should be pruned
* @param offset
* the offset at which children should be pruned. All children at and after this index will be
* removed from the tree.
*/
protected void prune(final Object parent, final int offset) {
WorkbenchJob updateJob = new WorkbenchJob("DeferredTree") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor updateMonitor) {
// Cancel the job if the tree viewer got closed
if (fViewer.getControl().isDisposed()) {
return Status.CANCEL_STATUS;
}
fViewer.prune(parent, offset);
return Status.OK_STATUS;
}
};
updateJob.setSystem(true);
updateJob.setPriority(Job.INTERACTIVE);
updateJob.schedule();
}
/**
* Run a job to clear the placeholder. This is used when the update for the tree is complete so that the
* user is aware that no more updates are pending.
*
* @param placeholder
*/
protected void runClearPlaceholderJob(final PendingUpdateAdapter placeholder) {
if (placeholder == null || placeholder.isRemoved() || !PlatformUI.isWorkbenchRunning()) {
return;
}
// Clear the placeholder if it is still there
WorkbenchJob clearJob = new WorkbenchJob("DeferredTreeContentManager_ClearJob") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (!placeholder.isRemoved()) {
Control control = fViewer.getControl();
if (control.isDisposed()) {
return Status.CANCEL_STATUS;
}
fViewer.remove(placeholder);
placeholder.setRemoved(true);
}
return Status.OK_STATUS;
}
};
clearJob.setSystem(true);
clearJob.schedule();
}
protected String getFetchJobName(Object parent, IDeferredWorkbenchAdapter adapter) {
return "RemoteTreeContentManager"; //$NON-NLS-1$
}
/**
* Return the IDeferredWorkbenchAdapter for element or the element if it is an instance of
* IDeferredWorkbenchAdapter. If it does not exist return null.
*
* @param element
* @return IDeferredWorkbenchAdapter or <code>null</code>
*/
protected IDeferredWorkbenchAdapter getAdapter(Object element) {
if (element instanceof IDeferredWorkbenchAdapter) {
return (IDeferredWorkbenchAdapter) element;
}
if (!(element instanceof IAdaptable)) {
return null;
}
Object adapter = ((IAdaptable) element).getAdapter(IDeferredWorkbenchAdapter.class);
if (adapter == null) {
return null;
}
return (IDeferredWorkbenchAdapter) adapter;
}
protected void startFetchingDeferredChildren(final Object parent, final IDeferredWorkbenchAdapter adapter,
PendingUpdateAdapter placeholder) {
final IElementCollector collector = createElementCollector(parent, placeholder);
synchronized (fElementQueue) {
if (!fElementQueue.contains(parent)) {
fElementQueue.add(parent);
fCollectors.add(collector);
fAdapaters.add(adapter);
}
}
if (progressService == null) {
fFetchJob.schedule();
} else {
progressService.schedule(fFetchJob);
}
}
/**
* Provides an optimized lookup for determining if an element has children. This is required because
* elements that are populated lazilly can't answer <code>getChildren</code> just to determine the
* potential for children. Throw an AssertionFailedException if element is null.
*
* @param element
* The Object being tested. This should not be <code>null</code>.
* @return boolean <code>true</code> if there are potentially children.
* @throws RuntimeException
* if the element is null.
*/
public boolean mayHaveChildren(Object element) {
// Assert.isNotNull(element, ProgressMessages.DeferredTreeContentManager_NotDeferred);
IDeferredWorkbenchAdapter adapter = getAdapter(element);
return adapter != null && adapter.isContainer();
}
/**
* Cancels any content this provider is currently fetching.
*/
public void cancel() {
synchronized (fElementQueue) {
fFetchJob.cancel();
fElementQueue.clear();
fAdapaters.clear();
fCollectors.clear();
}
}
}