/******************************************************************************* * 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.cdt.internal.ui.util; 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 RemoteTreeViewer fViewer; private IWorkbenchSiteProgressService progressService; /** * Job to fetch children */ private Job fFetchJob = new FetchJob(); /** * Queue of parents to fetch children for, and * associated element collectors and deferred adapters. */ private List<Object> fElementQueue = new ArrayList<Object>(); private List<IElementCollector> fCollectors = new ArrayList<IElementCollector>(); private List<IDeferredWorkbenchAdapter> fAdapaters = new ArrayList<IDeferredWorkbenchAdapter>(); /** * Fetching children is done in a single background job. * This makes fetching single threaded/serial per view. */ class FetchJob extends Job { public FetchJob() { super("FetchJob"); //$NON-NLS-1$ setSystem(true); } /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */ @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 */ public class Collector implements IElementCollector { // number of children added to the tree int offset = 0; Object fParent; public Collector(Object parent) { fParent = parent; } /* * (non-Javadoc) * @see org.eclipse.jface.progress.IElementCollector#add(java.lang.Object, org.eclipse.core.runtime.IProgressMonitor) */ public void add(Object element, IProgressMonitor monitor) { add(new Object[] { element }, monitor); } /* * (non-Javadoc) * @see org.eclipse.jface.progress.IElementCollector#add(java.lang.Object[], org.eclipse.core.runtime.IProgressMonitor) */ public void add(Object[] elements, IProgressMonitor monitor) { Object[] filtered = fViewer.filter(elements); fViewer.getSorter().sort(fViewer, filtered); if (filtered.length > 0) { replaceChildren(fParent, filtered, offset, monitor); offset = offset + filtered.length; } } /* * (non-Javadoc) * * @see org.eclipse.jface.progress.IElementCollector#done() */ 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$ /* * (non-Javadoc) * * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) */ @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$ /* * (non-Javadoc) * * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) */ @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$ /* * (non-Javadoc) * * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) */ @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(); } /* (non-Javadoc) * @see org.eclipse.ui.progress.DeferredTreeContentManager#getFetchJobName(java.lang.Object, org.eclipse.ui.progress.IDeferredWorkbenchAdapter) */ 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(); } } }