| /******************************************************************************* |
| * 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(); |
| } |
| } |
| |
| } |