/*******************************************************************************
* Copyright (c) 2006, 2008 Wind River Systems, Inc. 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:
* Markus Schorn - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.ui.viewsupport;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.cdt.internal.ui.CUIMessages;
/**
* A TreeContentProvider that supports asyncronous computation of child nodes.
* <p>
* While a computation for children is in progress an object of type {@link AsyncTreeWorkInProgressNode}
* is returned as a child. On completion of the computation the viewer will be refreshed with the actual
* children.
*/
public abstract class AsyncTreeContentProvider implements ITreeContentProvider {
private static final int PRIORITY_LOW = 0;
private static final int PRIORITY_HIGH = 10;
protected static final Object[] NO_CHILDREN = new Object[0];
private Object fInput;
private HashMap<Object, Object[]> fChildNodes= new HashMap<Object, Object[]>();
private HashSet<Object> fHighPriorityTasks= new HashSet<Object>();
private HashSet<Object> fLowPriorityTasks= new HashSet<Object>();
private HashMap<Object, Object[]> fViewUpdates= new HashMap<Object, Object[]>();
private int fViewUpdateDelta;
private Job fJob;
private Display fDisplay;
private TreeViewer fTreeViewer= null;
private Runnable fScheduledViewupdate= null;
private HashSet<Object> fAutoexpand;
private Object fAutoSelect;
public AsyncTreeContentProvider(Display disp) {
fDisplay= disp;
fJob= new Job(CUIMessages.AsyncTreeContentProvider_JobName) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
return runJob(monitor);
}
};
fJob.setSystem(true);
}
/**
* {@inheritDoc}
* <p>
* This implementation returns the parent for nodes indicating asyncronous computation.
* It returns <code>null</code> for all other elements. It should be overridden and
* called by derived classes.
*/
public Object getParent(Object element) {
if (element instanceof AsyncTreeWorkInProgressNode) {
AsyncTreeWorkInProgressNode wipNode = (AsyncTreeWorkInProgressNode) element;
return wipNode.getParent();
}
return null;
}
/**
* Returns the child elements of the given parent element, or <code>null</code>.
* <p>
* The method is called within the UI-thread and shall therefore return null in case
* the computation of the children may take longer.
* </p>
* The result is neither modified by the content provider nor the viewer.
*
* @param parentElement the parent element
* @return an array of child elements, or <code>null</code>
*/
protected Object[] syncronouslyComputeChildren(Object parentElement) {
return null;
}
/**
* Returns the child elements of the given parent element.
* <p>
* The method is called outside the UI-thread. There is no need to report progress, the monitor
* is supplied such that implementations can check for cancel requests.
* </p>
* The result is neither modified by the content provider nor the viewer.
*
* @param parentElement the parent element
* @param monitor the monitor that can be checked for a cancel event.
* @return an array of child elements.
*/
protected Object[] asyncronouslyComputeChildren(Object parentElement, IProgressMonitor monitor) {
return NO_CHILDREN;
}
/**
* Clears all caches and stops asyncronous computations. As a consequence
* child nodes requested by the viewer have to be computed from scratch.
* <p>
* Derived classes may override this method but must call <code>super.clearCaches()</code>.
*/
protected void clear() {
fChildNodes.clear();
synchronized (fHighPriorityTasks) {
fScheduledViewupdate= null;
fHighPriorityTasks.clear();
fLowPriorityTasks.clear();
fViewUpdates.clear();
}
}
/**
* Recomputes all of the nodes, trying to keep the expanded state even with async
* computations.
*/
public void recompute() {
if (getInput() != null) {
fAutoexpand= new HashSet<Object>();
fAutoexpand.addAll(Arrays.asList(fTreeViewer.getVisibleExpandedElements()));
fAutoSelect= null;
fAutoSelect= ((IStructuredSelection) fTreeViewer.getSelection()).getFirstElement();
clear();
refreshViewer();
}
}
/**
* Refreshes the viewer
*/
private void refreshViewer() {
if (fTreeViewer != null) {
fTreeViewer.refresh();
}
}
final public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (newInput != oldInput) {
clear();
fInput= newInput;
}
if (viewer instanceof TreeViewer) {
fTreeViewer= (TreeViewer) viewer;
}
else {
fTreeViewer= null;
}
}
final public Object getInput() {
return fInput;
}
final public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
final public Object[] getChildren(Object parentElement) {
Object[] children = internalGetChildren(parentElement);
if (children == null) {
scheduleQuery(parentElement, PRIORITY_HIGH);
return new Object[] {new AsyncTreeWorkInProgressNode(parentElement)};
}
return children;
}
final public boolean hasChildren(Object element) {
assert Display.getCurrent() != null;
Object[] children= internalGetChildren(element);
if (children == null) {
scheduleQuery(element, PRIORITY_LOW);
return true;
}
return children.length > 0;
}
public void dispose() {
fTreeViewer= null;
clear();
}
private void scheduleQuery(Object element, int priority) {
synchronized(fHighPriorityTasks) {
if (priority == PRIORITY_HIGH) {
if (!fHighPriorityTasks.contains(element)) {
fHighPriorityTasks.add(element);
fLowPriorityTasks.remove(element);
}
}
else {
if (!fHighPriorityTasks.contains(element) &&
!fLowPriorityTasks.contains(element)) {
fLowPriorityTasks.add(element);
}
}
fJob.schedule();
}
}
private IStatus runJob(final IProgressMonitor monitor) {
monitor.beginTask(CUIMessages.AsyncTreeContentProvider_TaskName, IProgressMonitor.UNKNOWN);
try {
Object parent= getParentForNextTask();
while (parent != null) {
Object[] children= asyncronouslyComputeChildren(parent, monitor);
synchronized (fHighPriorityTasks) {
if (fHighPriorityTasks.remove(parent) || fLowPriorityTasks.remove(parent)) {
fViewUpdates.put(parent, children);
scheduleViewerUpdate();
}
}
parent= getParentForNextTask();
}
return Status.OK_STATUS;
}
finally {
monitor.done();
}
}
private void scheduleViewerUpdate() {
Runnable runme= null;
synchronized(fHighPriorityTasks) {
if (fScheduledViewupdate != null) {
return;
}
runme= fScheduledViewupdate= new Runnable(){
public void run() {
HashMap<Object, Object[]> updates= null;
synchronized(fHighPriorityTasks) {
if (fViewUpdates.isEmpty()) {
fScheduledViewupdate= null;
return;
}
if (fScheduledViewupdate != this) {
return;
}
updates= fViewUpdates;
fViewUpdates= new HashMap<Object, Object[]>();
}
fChildNodes.putAll(updates);
if (fTreeViewer instanceof ExtendedTreeViewer) {
((ExtendedTreeViewer) fTreeViewer).refresh(updates.keySet().toArray());
}
else if (fTreeViewer != null) {
for (Iterator<Object> iter = updates.keySet().iterator(); iter.hasNext();) {
fTreeViewer.refresh(iter.next());
}
}
for (Iterator<Object[]> iter = updates.values().iterator(); iter.hasNext();) {
checkForAutoExpand(iter.next());
}
fViewUpdateDelta= Math.min(1000, fViewUpdateDelta*2);
fDisplay.timerExec(fViewUpdateDelta, this);
}
};
}
try {
fViewUpdateDelta= 32;
fDisplay.asyncExec(runme);
}
catch (SWTException e) {
// display may have been disposed.
}
}
private void checkForAutoExpand(Object[] objects) {
if (fTreeViewer == null) {
return;
}
if (fAutoexpand != null && !fAutoexpand.isEmpty()) {
for (int i = 0; i < objects.length; i++) {
Object object = objects[i];
if (fAutoexpand.remove(object)) {
fTreeViewer.setExpandedState(object, true);
}
if (object.equals(fAutoSelect)) {
if (fTreeViewer.getSelection().isEmpty()) {
fTreeViewer.setSelection(new StructuredSelection(object));
}
fAutoSelect= null;
}
}
}
if (fAutoSelect != null) {
if (fTreeViewer.getSelection().isEmpty()) {
for (int i = 0; i < objects.length; i++) {
Object object = objects[i];
if (object.equals(fAutoSelect)) {
fTreeViewer.setSelection(new StructuredSelection(object));
fAutoSelect= null;
}
}
}
else {
fAutoSelect= null;
}
}
}
private final Object getParentForNextTask() {
synchronized(fHighPriorityTasks) {
Object parent= null;
if (!fHighPriorityTasks.isEmpty()) {
parent= fHighPriorityTasks.iterator().next();
}
else if (!fLowPriorityTasks.isEmpty()) {
parent= fLowPriorityTasks.iterator().next();
}
return parent;
}
}
private Object[] internalGetChildren(Object parentElement) {
assert Display.getCurrent() != null;
if (parentElement instanceof AsyncTreeWorkInProgressNode) {
return NO_CHILDREN;
}
Object[] children= fChildNodes.get(parentElement);
if (children == null) {
children= syncronouslyComputeChildren(parentElement);
if (children != null) {
final Object[] finalChildren= children;
fChildNodes.put(parentElement, children);
fDisplay.asyncExec(new Runnable() {
public void run() {
checkForAutoExpand(finalChildren);
}});
}
}
return children;
}
final protected Display getDisplay() {
return fDisplay;
}
}