blob: 2f8240a135ebed5138e530baf5abea94e6cf6111 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2009 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.ui.progress;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.progress.ProgressMessages;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.model.IWorkbenchAdapter;
/**
* The DeferredContentManager is a class that helps an ITreeContentProvider get
* its deferred input.
*
* <b>NOTE</b> AbstractTreeViewer#isExpandable may need to be implemented in
* AbstractTreeViewer subclasses with deferred content that use filtering as a
* call to #getChildren may be required to determine the correct state of the
* expanding control.
*
* AbstractTreeViewers which use this class may wish to sacrifice accuracy of
* the expandable state indicator for the performance benefits of deferring
* content.
*
* @see IDeferredWorkbenchAdapter
* @since 3.0
*/
public class DeferredTreeContentManager {
AbstractTreeViewer treeViewer;
IWorkbenchSiteProgressService progressService;
private ListenerList updateCompleteListenerList;
/**
* The DeferredContentFamily is a class used to keep track of a
* manager-object pair so that only jobs scheduled by the receiver are
* canceled by the receiver.
*
* @since 3.1
*
*/
class DeferredContentFamily {
protected DeferredTreeContentManager manager;
protected Object element;
/**
* Create a new instance of the receiver to define a family for object
* in a particular scheduling manager.
*
* @param schedulingManager
* @param object
*/
DeferredContentFamily(DeferredTreeContentManager schedulingManager,
Object object) {
this.manager = schedulingManager;
this.element = object;
}
}
/**
* Create a new instance of the receiver using the supplied content provider
* and viewer. Run any jobs using the site.
*
* @param provider
* @param viewer
* @param site
* @deprecated in 3.4. provider is not used by this class
*/
public DeferredTreeContentManager(ITreeContentProvider provider,
AbstractTreeViewer viewer, IWorkbenchPartSite site) {
this(viewer, site);
}
/**
* Create a new instance of the receiver using the supplied content provider
* and viewer.
*
* @param provider
* The content provider that will be updated
* @param viewer
* The tree viewer that the results are added to
* @deprecated in 3.4. provider is not used by this class
*/
public DeferredTreeContentManager(ITreeContentProvider provider,
AbstractTreeViewer viewer) {
this(viewer);
}
/**
* Create a new instance of the receiver using the supplied content provider
* and viewer. Run any jobs using the site.
*
* @param viewer
* @param site
*
* @since 3.4
*/
public DeferredTreeContentManager(AbstractTreeViewer viewer,
IWorkbenchPartSite site) {
this(viewer);
Object siteService = Util.getAdapter(site,
IWorkbenchSiteProgressService.class);
if (siteService != null) {
progressService = (IWorkbenchSiteProgressService) siteService;
}
}
/**
* Create a new instance of the receiver using the supplied content provider
* and viewer.
*
* @param viewer
* The tree viewer that the results are added to
*
* @since 3.4
*/
public DeferredTreeContentManager(AbstractTreeViewer viewer) {
treeViewer = viewer;
}
/**
* 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();
}
/**
* 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;
}
PendingUpdateAdapter placeholder = createPendingUpdateAdapter();
startFetchingDeferredChildren(parent, element, placeholder);
return new Object[] { placeholder };
}
/**
* Factory method for creating the pending update adapter representing the
* placeholder node. Subclasses may override.
*
* @return a pending update adapter
* @since 3.2
*/
protected PendingUpdateAdapter createPendingUpdateAdapter() {
return new PendingUpdateAdapter();
}
/**
* 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) {
return (IDeferredWorkbenchAdapter) Util.getAdapter(element,
IDeferredWorkbenchAdapter.class);
}
/**
* Starts a job and creates a collector for fetching the children of this
* deferred adapter. If children are waiting to be retrieved for this parent
* already, that job is cancelled and another is started.
*
* @param parent
* The parent object being filled in,
* @param adapter
* The adapter being used to fetch the children.
* @param placeholder
* The adapter that will be used to indicate that results are
* pending.
*/
protected void startFetchingDeferredChildren(final Object parent,
final IDeferredWorkbenchAdapter adapter,
final PendingUpdateAdapter placeholder) {
final IElementCollector collector = createElementCollector(parent,
placeholder);
// Cancel any jobs currently fetching children for the same parent
// instance.
cancel(parent);
String jobName = getFetchJobName(parent, adapter);
Job job = new Job(jobName) {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus run(IProgressMonitor monitor) {
adapter.fetchDeferredChildren(parent, collector, monitor);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.jobs.Job#belongsTo(java.lang.Object)
*/
public boolean belongsTo(Object family) {
if (family instanceof DeferredContentFamily) {
DeferredContentFamily contentFamily = (DeferredContentFamily) family;
if (contentFamily.manager == DeferredTreeContentManager.this) {
return isParent(contentFamily, parent);
}
}
return false;
}
/**
* Check if the parent of element is equal to the parent used in
* this job.
*
* @param family
* The DeferredContentFamily that defines a potential
* ancestor of the current parent in a particular
* manager.
* @param child
* The object to check against.
* @return boolean <code>true</code> if the child or one of its
* parents are the same as the element of the family.
*/
private boolean isParent(DeferredContentFamily family, Object child) {
if (family.element.equals(child)) {
return true;
}
IWorkbenchAdapter workbenchAdapter = getWorkbenchAdapter(child);
if (workbenchAdapter == null) {
return false;
}
Object elementParent = workbenchAdapter.getParent(child);
if (elementParent == null) {
return false;
}
return isParent(family, elementParent);
}
/**
* Get the workbench adapter for the element.
*
* @param element
* The object we are adapting to.
*/
private IWorkbenchAdapter getWorkbenchAdapter(Object element) {
return (IWorkbenchAdapter) Util.getAdapter(element,
IWorkbenchAdapter.class);
}
};
job.addJobChangeListener(new JobChangeAdapter() {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.JobChangeAdapter#done(org.eclipse.core.runtime.jobs.IJobChangeEvent)
*/
public void done(IJobChangeEvent event) {
runClearPlaceholderJob(placeholder);
}
});
job.setRule(adapter.getRule(parent));
if (progressService == null) {
job.schedule();
} else {
progressService.schedule(job);
}
}
/**
* Returns a name to use for the job that fetches children of the given
* parent. Subclasses may override. Default job name is parent's label.
*
* @param parent
* parent that children are to be fetched for
* @param adapter
* parent's deferred adapter
* @return job name
*/
protected String getFetchJobName(Object parent,
IDeferredWorkbenchAdapter adapter) {
return NLS.bind(
ProgressMessages.DeferredTreeContentManager_FetchingName,
adapter.getLabel(parent));
}
/**
* Create a UIJob to add the children to the parent in the tree viewer.
*
* @param parent
* @param children
* @param monitor
*/
protected void addChildren(final Object parent, final Object[] children,
IProgressMonitor monitor) {
WorkbenchJob updateJob = new WorkbenchJob(
ProgressMessages.DeferredTreeContentManager_AddingChildren) {
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus runInUIThread(IProgressMonitor updateMonitor) {
// Cancel the job if the tree viewer got closed
if (treeViewer.getControl().isDisposed()
|| updateMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
treeViewer.add(parent, children);
return Status.OK_STATUS;
}
};
updateJob.setSystem(true);
updateJob.schedule();
}
/**
* Return whether or not the element is or adapts to an
* IDeferredWorkbenchAdapter.
*
* @param element
* @return boolean <code>true</code> if the element is an
* IDeferredWorkbenchAdapter
*/
public boolean isDeferredAdapter(Object element) {
return getAdapter(element) != null;
}
/**
* 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.isRemoved() || !PlatformUI.isWorkbenchRunning()) {
return;
}
// Clear the placeholder if it is still there
WorkbenchJob clearJob = new WorkbenchJob(
ProgressMessages.DeferredTreeContentManager_ClearJob) {
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus runInUIThread(IProgressMonitor monitor) {
if (!placeholder.isRemoved()) {
Control control = treeViewer.getControl();
if (control.isDisposed()) {
return Status.CANCEL_STATUS;
}
treeViewer.remove(placeholder);
placeholder.setRemoved(true);
}
return Status.OK_STATUS;
}
};
clearJob.setSystem(true);
if (updateCompleteListenerList != null) {
Object[] listeners = updateCompleteListenerList.getListeners();
for (int i = 0; i < listeners.length; i++) {
clearJob
.addJobChangeListener((IJobChangeListener) listeners[i]);
}
}
clearJob.schedule();
}
/**
* Cancel all jobs that are fetching content for the given parent or any of
* its children.
*
* @param parent
*/
public void cancel(Object parent) {
if (parent == null) {
return;
}
Job.getJobManager().cancel(new DeferredContentFamily(this, parent));
}
/**
* 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.
* @return IElementCollector
*/
protected IElementCollector createElementCollector(final Object parent,
final PendingUpdateAdapter placeholder) {
return new IElementCollector() {
/*
* (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) {
addChildren(parent, elements, monitor);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.progress.IElementCollector#done()
*/
public void done() {
runClearPlaceholderJob(placeholder);
}
};
}
/**
* Add a listener to list of update complete listeners. These listeners are
* attached to the job that updates the viewer content (clears the pending
* entry, etc.) after all deferred content has been retrieved.
*
* This method has no effect if the listener has already been added to the
* list of listeners.
*
* Since 3.6, this listener is added to a list of listeners rather than
* replacing the previously added listener. For backward compatibility,
* adding a null listener will be interpreted as removal of a listener if
* only one listener has been registered.
*
* @param listener
* the listener to add to the list of update listeners
* @since 3.4
*/
public void addUpdateCompleteListener(IJobChangeListener listener){
// Maintain backward compatibility.
// Earlier only one listener was supported, so it can be removed by
// passing null
if (listener == null && updateCompleteListenerList != null) {
Object[] listeners = updateCompleteListenerList.getListeners();
if (listeners.length == 1) {
removeUpdateCompleteListener((IJobChangeListener) listeners[0]);
}
} else {
if (updateCompleteListenerList == null) {
updateCompleteListenerList = new ListenerList();
}
updateCompleteListenerList.add(listener);
}
}
/**
* Removes the listener from the list of update listeners that are attached
* to the job that updates the viewer content (clears the pending entry,
* etc.) after all deferred content has been retrieved. If the listener is
* already attached to a running job, it is not removed, but it will not be
* added to any subsequent jobs that are run.
*
* This method has no effect if the listener was not previously added to the
* listener list.
*
* @param listener
* the listener to be removed
* @since 3.6
*/
public void removeUpdateCompleteListener(IJobChangeListener listener) {
if (updateCompleteListenerList != null) {
updateCompleteListenerList.remove(listener);
}
}
}