001: /*******************************************************************************
002: * Copyright (c) 2003, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.progress;
011:
012: import org.eclipse.core.runtime.Assert;
013: import org.eclipse.core.runtime.IProgressMonitor;
014: import org.eclipse.core.runtime.IStatus;
015: import org.eclipse.core.runtime.Platform;
016: import org.eclipse.core.runtime.Status;
017: import org.eclipse.core.runtime.jobs.IJobChangeEvent;
018: import org.eclipse.core.runtime.jobs.Job;
019: import org.eclipse.core.runtime.jobs.JobChangeAdapter;
020: import org.eclipse.jface.viewers.AbstractTreeViewer;
021: import org.eclipse.jface.viewers.ITreeContentProvider;
022: import org.eclipse.osgi.util.NLS;
023: import org.eclipse.swt.widgets.Control;
024: import org.eclipse.ui.IWorkbenchPartSite;
025: import org.eclipse.ui.PlatformUI;
026: import org.eclipse.ui.internal.progress.ProgressMessages;
027: import org.eclipse.ui.internal.util.Util;
028: import org.eclipse.ui.model.IWorkbenchAdapter;
029:
030: /**
031: * The DeferredContentManager is a class that helps an ITreeContentProvider get
032: * its deferred input.
033: *
034: * <b>NOTE</b> AbstractTreeViewer#isExpandable may need to
035: * be implemented in AbstractTreeViewer subclasses with
036: * deferred content that use filtering as a call to
037: * #getChildren may be required to determine the correct
038: * state of the expanding control.
039: *
040: * AbstractTreeViewers which use this class may wish to
041: * sacrifice accuracy of the expandable state indicator for the
042: * performance benefits of deferring content.
043: *
044: * @see IDeferredWorkbenchAdapter
045: * @since 3.0
046: */
047: public class DeferredTreeContentManager {
048: ITreeContentProvider contentProvider;
049:
050: AbstractTreeViewer treeViewer;
051:
052: IWorkbenchSiteProgressService progressService;
053:
054: /**
055: * The DeferredContentFamily is a class used to keep track of a
056: * manager-object pair so that only jobs scheduled by the receiver
057: * are cancelled by the receiver.
058: * @since 3.1
059: *
060: */
061: class DeferredContentFamily {
062: protected DeferredTreeContentManager manager;
063: protected Object element;
064:
065: /**
066: * Create a new instance of the receiver to define a family
067: * for object in a particular scheduling manager.
068: * @param schedulingManager
069: * @param object
070: */
071: DeferredContentFamily(
072: DeferredTreeContentManager schedulingManager,
073: Object object) {
074: this .manager = schedulingManager;
075: this .element = object;
076: }
077: }
078:
079: /**
080: * Create a new instance of the receiver using the supplied content
081: * provider and viewer. Run any jobs using the site.
082: *
083: * @param provider
084: * @param viewer
085: * @param site
086: */
087: public DeferredTreeContentManager(ITreeContentProvider provider,
088: AbstractTreeViewer viewer, IWorkbenchPartSite site) {
089: this (provider, viewer);
090: Object siteService = Util.getAdapter(site,
091: IWorkbenchSiteProgressService.class);
092: if (siteService != null) {
093: progressService = (IWorkbenchSiteProgressService) siteService;
094: }
095: }
096:
097: /**
098: * Create a new instance of the receiver using the supplied content
099: * provider and viewer.
100: *
101: * @param provider The content provider that will be updated
102: * @param viewer The tree viewer that the results are added to
103: */
104: public DeferredTreeContentManager(ITreeContentProvider provider,
105: AbstractTreeViewer viewer) {
106: contentProvider = provider;
107: treeViewer = viewer;
108: }
109:
110: /**
111: * Provides an optimized lookup for determining if an element has children.
112: * This is required because elements that are populated lazilly can't
113: * answer <code>getChildren</code> just to determine the potential for
114: * children. Throw an AssertionFailedException if element is null.
115: *
116: * @param element The Object being tested. This should not be
117: * <code>null</code>.
118: * @return boolean <code>true</code> if there are potentially children.
119: * @throws RuntimeException if the element is null.
120: */
121: public boolean mayHaveChildren(Object element) {
122: Assert
123: .isNotNull(
124: element,
125: ProgressMessages.DeferredTreeContentManager_NotDeferred);
126: IDeferredWorkbenchAdapter adapter = getAdapter(element);
127: return adapter != null && adapter.isContainer();
128: }
129:
130: /**
131: * Returns the child elements of the given element, or in the case of a
132: * deferred element, returns a placeholder. If a deferred element is used, a
133: * job is created to fetch the children in the background.
134: *
135: * @param parent
136: * The parent object.
137: * @return Object[] or <code>null</code> if parent is not an instance of
138: * IDeferredWorkbenchAdapter.
139: */
140: public Object[] getChildren(final Object parent) {
141: IDeferredWorkbenchAdapter element = getAdapter(parent);
142: if (element == null) {
143: return null;
144: }
145: PendingUpdateAdapter placeholder = createPendingUpdateAdapter();
146: startFetchingDeferredChildren(parent, element, placeholder);
147: return new Object[] { placeholder };
148: }
149:
150: /**
151: * Factory method for creating the pending update adapter representing the
152: * placeholder node. Subclasses may override.
153: *
154: * @return a pending update adapter
155: * @since 3.2
156: */
157: protected PendingUpdateAdapter createPendingUpdateAdapter() {
158: return new PendingUpdateAdapter();
159: }
160:
161: /**
162: * Return the IDeferredWorkbenchAdapter for element or the element if it is
163: * an instance of IDeferredWorkbenchAdapter. If it does not exist return
164: * null.
165: *
166: * @param element
167: * @return IDeferredWorkbenchAdapter or <code>null</code>
168: */
169: protected IDeferredWorkbenchAdapter getAdapter(Object element) {
170: return (IDeferredWorkbenchAdapter) Util.getAdapter(element,
171: IDeferredWorkbenchAdapter.class);
172: }
173:
174: /**
175: * Starts a job and creates a collector for fetching the children of this
176: * deferred adapter. If children are waiting to be retrieved for this parent
177: * already, that job is cancelled and another is started.
178: *
179: * @param parent
180: * The parent object being filled in,
181: * @param adapter
182: * The adapter being used to fetch the children.
183: * @param placeholder
184: * The adapter that will be used to indicate that results are
185: * pending.
186: */
187: protected void startFetchingDeferredChildren(final Object parent,
188: final IDeferredWorkbenchAdapter adapter,
189: final PendingUpdateAdapter placeholder) {
190: final IElementCollector collector = createElementCollector(
191: parent, placeholder);
192: // Cancel any jobs currently fetching children for the same parent
193: // instance.
194: cancel(parent);
195: String jobName = getFetchJobName(parent, adapter);
196: Job job = new Job(jobName) {
197: /* (non-Javadoc)
198: * @see org.eclipse.core.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
199: */
200: public IStatus run(IProgressMonitor monitor) {
201: adapter.fetchDeferredChildren(parent, collector,
202: monitor);
203: if (monitor.isCanceled()) {
204: return Status.CANCEL_STATUS;
205: }
206: return Status.OK_STATUS;
207: }
208:
209: /* (non-Javadoc)
210: * @see org.eclipse.core.jobs.Job#belongsTo(java.lang.Object)
211: */
212: public boolean belongsTo(Object family) {
213: if (family instanceof DeferredContentFamily) {
214: DeferredContentFamily contentFamily = (DeferredContentFamily) family;
215: if (contentFamily.manager == DeferredTreeContentManager.this ) {
216: return isParent(contentFamily, parent);
217: }
218: }
219: return false;
220:
221: }
222:
223: /**
224: * Check if the parent of element is equal to the parent used in
225: * this job.
226: *
227: * @param family
228: * The DeferredContentFamily that defines a potential
229: * ancestor of the current parent in a particualr manager.
230: * @param child
231: * The object to check against.
232: * @return boolean <code>true</code> if the child or one of its
233: * parents are the same as the element of the family.
234: */
235: private boolean isParent(DeferredContentFamily family,
236: Object child) {
237: if (family.element.equals(child)) {
238: return true;
239: }
240: IWorkbenchAdapter workbenchAdapter = getWorkbenchAdapter(child);
241: if (workbenchAdapter == null) {
242: return false;
243: }
244: Object elementParent = workbenchAdapter
245: .getParent(child);
246: if (elementParent == null) {
247: return false;
248: }
249: return isParent(family, elementParent);
250: }
251:
252: /**
253: * Get the workbench adapter for the element.
254: *
255: * @param element
256: * The object we are adapting to.
257: */
258: private IWorkbenchAdapter getWorkbenchAdapter(Object element) {
259: return (IWorkbenchAdapter) Util.getAdapter(element,
260: IWorkbenchAdapter.class);
261: }
262: };
263: job.addJobChangeListener(new JobChangeAdapter() {
264: /*
265: * (non-Javadoc)
266: *
267: * @see org.eclipse.core.runtime.jobs.JobChangeAdapter#done(org.eclipse.core.runtime.jobs.IJobChangeEvent)
268: */
269: public void done(IJobChangeEvent event) {
270: runClearPlaceholderJob(placeholder);
271: }
272: });
273: job.setRule(adapter.getRule(parent));
274: if (progressService == null) {
275: job.schedule();
276: } else {
277: progressService.schedule(job);
278: }
279: }
280:
281: /**
282: * Returns a name to use for the job that fetches children of the given parent.
283: * Subclasses may override. Default job name is parent's label.
284: *
285: * @param parent parent that children are to be fetched for
286: * @param adapter parent's deferred adapter
287: * @return job name
288: */
289: protected String getFetchJobName(Object parent,
290: IDeferredWorkbenchAdapter adapter) {
291: return NLS
292: .bind(
293: ProgressMessages.DeferredTreeContentManager_FetchingName,
294: adapter.getLabel(parent));
295: }
296:
297: /**
298: * Create a UIJob to add the children to the parent in the tree viewer.
299: *
300: * @param parent
301: * @param children
302: * @param monitor
303: */
304: protected void addChildren(final Object parent,
305: final Object[] children, IProgressMonitor monitor) {
306: WorkbenchJob updateJob = new WorkbenchJob(
307: ProgressMessages.DeferredTreeContentManager_AddingChildren) {
308: /*
309: * (non-Javadoc)
310: *
311: * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
312: */
313: public IStatus runInUIThread(IProgressMonitor updateMonitor) {
314: //Cancel the job if the tree viewer got closed
315: if (treeViewer.getControl().isDisposed()
316: || updateMonitor.isCanceled()) {
317: return Status.CANCEL_STATUS;
318: }
319: treeViewer.add(parent, children);
320: return Status.OK_STATUS;
321: }
322: };
323: updateJob.setSystem(true);
324: updateJob.schedule();
325:
326: }
327:
328: /**
329: * Return whether or not the element is or adapts to an
330: * IDeferredWorkbenchAdapter.
331: *
332: * @param element
333: * @return boolean <code>true</code> if the element is an
334: * IDeferredWorkbenchAdapter
335: */
336: public boolean isDeferredAdapter(Object element) {
337: return getAdapter(element) != null;
338: }
339:
340: /**
341: * Run a job to clear the placeholder. This is used when the update
342: * for the tree is complete so that the user is aware that no more
343: * updates are pending.
344: *
345: * @param placeholder
346: */
347: protected void runClearPlaceholderJob(
348: final PendingUpdateAdapter placeholder) {
349: if (placeholder.isRemoved() || !PlatformUI.isWorkbenchRunning()) {
350: return;
351: }
352: //Clear the placeholder if it is still there
353: WorkbenchJob clearJob = new WorkbenchJob(
354: ProgressMessages.DeferredTreeContentManager_ClearJob) {
355: /*
356: * (non-Javadoc)
357: *
358: * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
359: */
360: public IStatus runInUIThread(IProgressMonitor monitor) {
361: if (!placeholder.isRemoved()) {
362: Control control = treeViewer.getControl();
363: if (control.isDisposed()) {
364: return Status.CANCEL_STATUS;
365: }
366: treeViewer.remove(placeholder);
367: placeholder.setRemoved(true);
368: }
369: return Status.OK_STATUS;
370: }
371: };
372: clearJob.setSystem(true);
373: clearJob.schedule();
374: }
375:
376: /**
377: * Cancel all jobs that are fetching content for the given parent or any of
378: * its children.
379: *
380: * @param parent
381: */
382: public void cancel(Object parent) {
383: if (parent == null) {
384: return;
385: }
386:
387: Platform.getJobManager().cancel(
388: new DeferredContentFamily(this , parent));
389: }
390:
391: /**
392: * Create the element collector for the receiver.
393: *@param parent
394: * The parent object being filled in,
395: * @param placeholder
396: * The adapter that will be used to indicate that results are
397: * pending.
398: * @return IElementCollector
399: */
400: protected IElementCollector createElementCollector(
401: final Object parent, final PendingUpdateAdapter placeholder) {
402: return new IElementCollector() {
403: /*
404: * (non-Javadoc)
405: * @see org.eclipse.jface.progress.IElementCollector#add(java.lang.Object, org.eclipse.core.runtime.IProgressMonitor)
406: */
407: public void add(Object element, IProgressMonitor monitor) {
408: add(new Object[] { element }, monitor);
409: }
410:
411: /*
412: * (non-Javadoc)
413: * @see org.eclipse.jface.progress.IElementCollector#add(java.lang.Object[], org.eclipse.core.runtime.IProgressMonitor)
414: */
415: public void add(Object[] elements, IProgressMonitor monitor) {
416: addChildren(parent, elements, monitor);
417: }
418:
419: /*
420: * (non-Javadoc)
421: *
422: * @see org.eclipse.jface.progress.IElementCollector#done()
423: */
424: public void done() {
425: runClearPlaceholderJob(placeholder);
426: }
427: };
428: }
429: }
|