blob: 133df6b70e4a1d741fdaaa394b313b8cad7e9796 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 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
* Pawel Piech - Wind River - Bug 205335: ModelContentProvider does not cancel stale updates when switching viewer input
* Wind River Systems - Fix for viewer state save/restore [188704]
* Pawel Piech (Wind River) - added support for a virtual tree model viewer (Bug 242489)
*******************************************************************************/
package org.eclipse.debug.internal.ui.viewers.model;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.IRequest;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementCompareRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxyFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxyFactory2;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* Content provider for a virtual viewer.
*
* @since 3.3
*/
abstract class ModelContentProvider implements IContentProvider, IModelChangedListener {
private ITreeModelContentProviderTarget fViewer;
/**
* Mask used to filter delta updates coming from the model.
*/
private int fModelDeltaMask = ~0;
/**
* Map tree paths to model proxy responsible for element
*
* Used to install different model proxy instances for one element depending
* on the tree path.
*/
private Map fTreeModelProxies = new HashMap(); // tree model proxy by
// element tree path
/**
* Map element to model proxy responsible for it.
*
* Used to install a single model proxy which is responsible for all
* instances of an element in the model tree.
*/
private Map fModelProxies = new HashMap(); // model proxy by element
/**
* Map of nodes that have been filtered from the viewer.
*/
private FilterTransform fTransform = new FilterTransform();
/**
* Model listeners
*/
private ListenerList fModelListeners = new ListenerList();
/**
* Viewer update listeners
*/
private ListenerList fUpdateListeners = new ListenerList();
/**
* State update listeners
*/
private ListenerList fStateUpdateListeners = new ListenerList();
/**
* Map of updates in progress: element path -> list of requests
*/
private Map fRequestsInProgress = new HashMap();
/**
* Map of dependent requests waiting for parent requests to complete:
* element path -> list of requests
*/
private Map fWaitingRequests = new HashMap();
/**
* Map of viewer states keyed by viewer input mementos
*/
private Map fViewerStates = new LRUMap(20);
/**
* Pending viewer state to be restored
*/
private ModelDelta fPendingState = null;
private static class CompareRequestKey {
CompareRequestKey(TreePath path, IModelDelta delta) {
fPath = path;
fDelta = delta;
}
TreePath fPath;
IModelDelta fDelta;
public boolean equals(Object obj) {
if (obj instanceof CompareRequestKey) {
CompareRequestKey key = (CompareRequestKey) obj;
return key.fDelta.equals(fDelta) && key.fPath.equals(fPath);
}
return false;
}
public int hashCode() {
return fDelta.hashCode() + fPath.hashCode();
}
}
private Map fCompareRequestsInProgress = new LinkedHashMap();
/**
* Set of IMementoManager's that are currently saving state
*/
private Set fPendingStateSaves = new HashSet();
/**
* Used to queue a viewer input for state restore
*/
private Object fQueuedRestore = null;
/**
* Dummy marker element used in the state delta. The marker indicates that a
* given element in the pending state delta has been removed. It replaces
* the original element so that it may optionally be garbage collected.
*/
private final static String ELEMENT_REMOVED = "ELEMENT_REMOVED"; //$NON-NLS-1$
/**
* Used to determine when restoration delta has been processed
*/
protected class CheckState implements IModelDeltaVisitor {
private boolean complete = true;
private IModelDelta topDelta = null;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.provisional.IModelDeltaVisitor
* #visit(org.eclipse.debug.internal.ui.viewers.provisional.IModelDelta,
* int)
*/
public boolean visit(IModelDelta delta, int depth) {
// Filster out the CONTENT flags from the delta flags, the content
// flag is only used as a marker indicating that all the sub-elements
// of a given delta have been retrieved.
int flags = (delta.getFlags() & ~IModelDelta.CONTENT);
if (flags != IModelDelta.NO_CHANGE) {
IModelDelta parentDelta = delta.getParentDelta();
// Remove the delta if :
// - The parent delta has no more flags on it (the content flag is removed as well),
// which means that parent element's children have been completely exposed.
// - There are no more pending updates for the element.
// - If element is a memento, there are no state requests pending.
if (parentDelta != null && parentDelta.getFlags() == IModelDelta.NO_CHANGE) {
TreePath deltaPath = getViewerTreePath(delta);
if ( !areElementUpdatesPending(deltaPath) &&
(!(delta.getElement() instanceof IMemento) || !areMementoUpdatesPending(delta)) )
{
removeDelta(delta);
return false;
}
}
if (flags == IModelDelta.REVEAL && !(delta.getElement() instanceof IMemento)) {
topDelta = delta;
} else {
complete = false;
return false;
}
}
return true;
}
public boolean isComplete() {
return complete;
}
public IModelDelta getTopItemDelta() {
return topDelta;
}
private boolean areElementUpdatesPending(TreePath path) {
synchronized (fRequestsInProgress) {
TreePath parentPath = path.getParentPath();
List requests = (List) fWaitingRequests.get(path);
if (requests != null) {
for (int i = 0; i < requests.size(); i++) {
ViewerUpdateMonitor update = (ViewerUpdateMonitor) requests.get(i);
if (update instanceof ChildrenUpdate) {
return true;
}
}
}
requests = (List) fWaitingRequests.get(parentPath);
if (requests != null) {
for (int i = 0; i < requests.size(); i++) {
ViewerUpdateMonitor update = (ViewerUpdateMonitor) requests.get(i);
if (update.containsUpdate(path)) {
return true;
}
}
}
requests = (List) fRequestsInProgress.get(path);
if (requests != null) {
for (int i = 0; i < requests.size(); i++) {
ViewerUpdateMonitor update = (ViewerUpdateMonitor) requests.get(i);
if (update instanceof ChildrenUpdate) {
return true;
}
}
}
requests = (List) fRequestsInProgress.get(parentPath);
if (requests != null) {
for (int i = 0; i < requests.size(); i++) {
ViewerUpdateMonitor update = (ViewerUpdateMonitor) requests.get(i);
if (update.getElement().equals(path.getLastSegment())) {
return true;
}
}
}
}
return false;
}
private boolean areMementoUpdatesPending(IModelDelta delta) {
for (Iterator itr = fCompareRequestsInProgress.keySet().iterator(); itr.hasNext();) {
CompareRequestKey key = (CompareRequestKey) itr.next();
if (delta.getElement().equals(key.fDelta.getElement())) {
return true;
}
}
return false;
}
private void removeDelta(IModelDelta delta) {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tRESTORE REMOVED: " + delta.getElement()); //$NON-NLS-1$
}
delta.accept(new IModelDeltaVisitor() {
public boolean visit(IModelDelta _visitorDelta, int depth) {
ModelDelta visitorDelta = (ModelDelta) _visitorDelta;
visitorDelta.setElement(ELEMENT_REMOVED);
visitorDelta.setFlags(IModelDelta.NO_CHANGE);
return true;
}
});
}
}
/**
* LRU cache for viewer states
*/
class LRUMap extends LinkedHashMap {
private static final long serialVersionUID = 1L;
private int fMaxSize;
LRUMap(int maxSize) {
super();
fMaxSize = maxSize;
}
protected boolean removeEldestEntry(Entry eldest) {
return size() > fMaxSize;
}
}
/**
* Update type constants
*/
static final int UPDATE_SEQUENCE_BEGINS = 0;
static final int UPDATE_SEQUENCE_COMPLETE = 1;
static final int UPDATE_BEGINS = 2;
static final int UPDATE_COMPLETE = 3;
/**
* Additional state update type constants
*/
static final int STATE_SAVE_SEQUENCE_BEGINS = 4;
static final int STATE_SAVE_SEQUENCE_COMPLETE = 5;
static final int STATE_RESTORE_SEQUENCE_BEGINS = 6;
static final int STATE_RESTORE_SEQUENCE_COMPLETE = 7;
/**
* Constant for an empty tree path.
*/
protected static final TreePath EMPTY_TREE_PATH = new TreePath(new Object[] {});
// debug flags
public static String DEBUG_PRESENTATION_ID = null;
public static boolean DEBUG_CONTENT_PROVIDER = false;
public static boolean DEBUG_UPDATE_SEQUENCE = false;
public static boolean DEBUG_STATE_SAVE_RESTORE = false;
public static boolean DEBUG_DELTAS = false;
static {
DEBUG_PRESENTATION_ID = Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/presentationId"); //$NON-NLS-1$
if (!DebugUIPlugin.DEBUG || "".equals(DEBUG_PRESENTATION_ID)) { //$NON-NLS-1$
DEBUG_PRESENTATION_ID = null;
}
DEBUG_CONTENT_PROVIDER = DebugUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$
Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/contentProvider")); //$NON-NLS-1$
DEBUG_UPDATE_SEQUENCE = DebugUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$
Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/updateSequence")); //$NON-NLS-1$
DEBUG_STATE_SAVE_RESTORE = DebugUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$
Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/stateSaveRestore")); //$NON-NLS-1$
DEBUG_DELTAS = DebugUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$
Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/deltas")); //$NON-NLS-1$
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
public synchronized void dispose() {
// cancel pending updates
synchronized (fRequestsInProgress) {
Iterator iterator = fRequestsInProgress.values().iterator();
while (iterator.hasNext()) {
List requests = (List) iterator.next();
Iterator reqIter = requests.iterator();
while (reqIter.hasNext()) {
((IRequest) reqIter.next()).cancel();
}
}
fWaitingRequests.clear();
}
fModelListeners.clear();
fUpdateListeners.clear();
fStateUpdateListeners.clear();
disposeAllModelProxies();
fViewer = null;
}
public synchronized boolean isDisposed() {
return fViewer == null;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface
* .viewers.Viewer, java.lang.Object, java.lang.Object)
*/
public synchronized void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
fViewer = (ITreeModelContentProviderTarget) viewer;
if (oldInput != null) {
for (Iterator itr = fCompareRequestsInProgress.values().iterator(); itr.hasNext();) {
((ElementCompareRequest) itr.next()).cancel();
itr.remove();
}
saveViewerState(oldInput);
}
if (newInput != oldInput) {
cancelSubtreeUpdates(TreePath.EMPTY);
disposeAllModelProxies();
cancelSubtreeUpdates(TreePath.EMPTY);
fTransform.clear();
if (newInput != null) {
installModelProxy(newInput, TreePath.EMPTY);
restoreViewerState(newInput);
}
}
}
/**
* Restores the viewer state unless a save is taking place. If a save is
* taking place, the restore is queued.
*
* @param input
* viewer input
*/
protected synchronized void restoreViewerState(final Object input) {
fPendingState = null;
if (isSavingState()) {
fQueuedRestore = input;
} else {
startRestoreViewerState(input);
}
}
/**
* Restores viewer state for the given input
*
* @param input
* viewer input
*/
private synchronized void startRestoreViewerState(final Object input) {
fPendingState = null;
final IElementMementoProvider defaultProvider = ViewerAdapterService.getMementoProvider(input);
if (defaultProvider != null) {
// build a model delta representing expansion and selection state
final ModelDelta delta = new ModelDelta(input, IModelDelta.NO_CHANGE);
final XMLMemento inputMemento = XMLMemento.createWriteRoot("VIEWER_INPUT_MEMENTO"); //$NON-NLS-1$
final IMementoManager manager = new IMementoManager() {
private IElementMementoRequest fRequest;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .
* IMementoManager#requestComplete(org.eclipse.debug.internal.ui
* .viewers.model.provisional.IElementMementoRequest)
*/
public synchronized void requestComplete(IElementMementoRequest request) {
notifyStateUpdate(input, UPDATE_COMPLETE, request);
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext()
.getId()))) {
System.out.println("\tSTATE END: " + request); //$NON-NLS-1$
}
if (!request.isCanceled() && (request.getStatus() == null || request.getStatus().isOK())) {
XMLMemento keyMemento = (XMLMemento) delta.getElement();
StringWriter writer = new StringWriter();
try {
keyMemento.save(writer);
final String keyMementoString = writer.toString();
final ModelDelta stateDelta = (ModelDelta) fViewerStates.get(keyMementoString);
if (stateDelta != null) {
stateDelta.setElement(input);
// begin restoration
UIJob job = new UIJob("restore state") { //$NON-NLS-1$
public IStatus runInUIThread(IProgressMonitor monitor) {
if (!isDisposed() && input.equals(getViewer().getInput())) {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID
.equals(getPresentationContext().getId()))) {
System.out.println("\tRESTORE: " + stateDelta.toString()); //$NON-NLS-1$
}
fViewerStates.remove(keyMementoString);
fPendingState = stateDelta;
doInitialRestore(fPendingState);
} else {
notifyStateUpdate(input, STATE_RESTORE_SEQUENCE_BEGINS, null);
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
} else {
notifyStateUpdate(input, STATE_RESTORE_SEQUENCE_BEGINS, null);
}
} catch (IOException e) {
DebugUIPlugin.log(e);
}
} else {
notifyStateUpdate(input, STATE_RESTORE_SEQUENCE_BEGINS, null);
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .IMementoManager#processReqeusts()
*/
public void processReqeusts() {
notifyStateUpdate(input, STATE_RESTORE_SEQUENCE_BEGINS, null);
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext()
.getId()))) {
System.out.println("\tSTATE BEGIN: " + fRequest); //$NON-NLS-1$
}
notifyStateUpdate(input, UPDATE_BEGINS, fRequest);
defaultProvider.encodeElements(new IElementMementoRequest[] { fRequest });
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .
* IMementoManager#addRequest(org.eclipse.debug.internal.ui.viewers
* .model.provisional.IElementMementoRequest)
*/
public synchronized void addRequest(IElementMementoRequest req) {
fRequest = req;
}
};
manager.addRequest(new ElementMementoRequest(ModelContentProvider.this, getViewer().getInput(), manager,
getPresentationContext(), delta.getElement(), getViewerTreePath(delta), inputMemento, delta));
manager.processReqeusts();
}
}
/**
* Restore selection/expansion based on items already in the viewer
*/
protected abstract void doInitialRestore(ModelDelta delta);
/**
* @param delta
*/
abstract void doRestore(final ModelDelta delta, boolean knowsHasChildren, boolean knowsChildCount,
boolean checkChildrenRealized);
protected void appendToPendingStateDelta(TreePath path) {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("STATE APPEND BEGIN: " + path.getLastSegment()); //$NON-NLS-1$
}
// build a model delta representing expansion and selection state
final ModelDelta appendDeltaRoot = new ModelDelta(getViewer().getInput(), IModelDelta.NO_CHANGE);
ModelDelta delta = appendDeltaRoot;
for (int i = 0; i < path.getSegmentCount(); i++) {
delta = delta.addNode(path.getSegment(i), IModelDelta.NO_CHANGE);
}
fViewer.saveElementState(path, delta, IModelDelta.COLLAPSE | IModelDelta.EXPAND | IModelDelta.SELECT);
// Append a marker CONTENT flag to all the delta nodes that contain the
// EXPAND node. These
// markers are used by the restore logic to know when a delta node can
// be removed.
delta.accept(new IModelDeltaVisitor() {
public boolean visit(IModelDelta delta, int depth) {
if ((delta.getFlags() & IModelDelta.EXPAND) != 0) {
((ModelDelta) delta).setFlags(delta.getFlags() | IModelDelta.CONTENT);
}
return true;
}
});
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tAPPEND DELTA: " + appendDeltaRoot); //$NON-NLS-1$
}
if (fPendingState != null) {
// If the restore for the current input was never completed,
// preserve
// that restore along with the restore that was completed.
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tAPPEND OUTSTANDING RESTORE: " + fPendingState); //$NON-NLS-1$
}
IModelDeltaVisitor pendingStateVisitor = new IModelDeltaVisitor() {
public boolean visit(IModelDelta pendingDeltaNode, int depth) {
// Ignore the top element.
if (pendingDeltaNode.getParentDelta() == null) {
return true;
}
// Find the node in the save delta which is the parent
// of to the current pending delta node.
// If the parent node cannot be found, it means that
// most likely the user collapsed the parent node before
// the children were ever expanded.
// If the pending state node already exists in the parent
// node, it is already processed and we can skip it.
// If the pending state node does not contain any flags,
// we can also skip it.
ModelDelta saveDeltaNode = findSaveDelta(appendDeltaRoot, pendingDeltaNode);
if (saveDeltaNode != null && !isDeltaInParent(pendingDeltaNode, saveDeltaNode)
&& pendingDeltaNode.getFlags() != IModelDelta.NO_CHANGE) {
saveDeltaNode.setChildCount(pendingDeltaNode.getParentDelta().getChildCount());
copyIntoDelta(pendingDeltaNode, saveDeltaNode);
} else {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext()
.getId()))) {
System.out.println("\tSKIPPED: " + pendingDeltaNode.getElement()); //$NON-NLS-1$
}
}
// If the pending delta node has a memento element, its
// children should also be mementos therefore the copy
// delta operation should have added all the children
// of this pending delta node into the save delta.
if (pendingDeltaNode.getElement() instanceof IMemento) {
return false;
} else {
return pendingDeltaNode.getChildCount() > 0;
}
}
};
fPendingState.accept(pendingStateVisitor);
}
if (appendDeltaRoot.getChildDeltas().length > 0) {
// Set the new delta root as the pending state delta.
if (fPendingState == null) {
notifyStateUpdate(appendDeltaRoot.getElement(), STATE_RESTORE_SEQUENCE_BEGINS, null);
}
fPendingState = appendDeltaRoot;
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("STATE APPEND COMPLETE " + fPendingState); //$NON-NLS-1$
}
} else {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("STATE APPEND CANCELED: No Data"); //$NON-NLS-1$
}
}
}
/**
* Perform any restoration required for the given tree path.
*
* @param path
*/
protected synchronized void doRestore(final TreePath path, final int modelIndex, final boolean knowsHasChildren,
final boolean knowsChildCount, final boolean checkChildrenRealized) {
if (fPendingState == null) {
return;
}
IModelDeltaVisitor visitor = new IModelDeltaVisitor() {
public boolean visit(final IModelDelta delta, int depth) {
Object element = delta.getElement();
Object potentialMatch = depth != 0 ? path.getSegment(depth - 1) : getViewer().getInput();
// Only process if the depth in the delta matches the tree path.
if (depth == path.getSegmentCount()) {
if (element instanceof IMemento) {
IElementMementoProvider provider = ViewerAdapterService.getMementoProvider(potentialMatch);
if (provider == null) {
provider = ViewerAdapterService.getMementoProvider(getViewer().getInput());
}
if (provider != null) {
CompareRequestKey key = new CompareRequestKey(path, delta);
ElementCompareRequest existingRequest = (ElementCompareRequest) fCompareRequestsInProgress
.get(key);
if (existingRequest != null) {
// Check all the running compare updates for a
// matching tree path.
// If found, just update the flags.
existingRequest.setKnowsHasChildren(knowsHasChildren);
existingRequest.setKnowsChildCount(knowsChildCount);
existingRequest.setCheckChildrenRealized(checkChildrenRealized);
} else {
// Start a new compare request
ElementCompareRequest compareRequest = new ElementCompareRequest(
ModelContentProvider.this, getViewer().getInput(), potentialMatch, path,
(IMemento) element, (ModelDelta) delta, modelIndex, knowsHasChildren,
knowsChildCount, checkChildrenRealized);
fCompareRequestsInProgress.put(key, compareRequest);
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID
.equals(getPresentationContext().getId()))) {
System.out.println("\tSTATE BEGIN: " + compareRequest); //$NON-NLS-1$
}
notifyStateUpdate(fPendingState.getElement(), UPDATE_BEGINS, compareRequest);
provider.compareElements(new IElementCompareRequest[] { compareRequest });
}
}
} else if (element.equals(potentialMatch)) {
// Element comparison already succeeded, and it matches
// our element.
// Call restore with delta to process the delta flags.
doRestore((ModelDelta) delta, knowsHasChildren, knowsChildCount, checkChildrenRealized);
}
return false;
}
// Only follow the paths that match the delta.
return element.equals(potentialMatch);
}
};
fPendingState.accept(visitor);
}
void compareFinished(ElementCompareRequest request, ModelDelta delta) {
notifyStateUpdate(request.getViewerInput(), UPDATE_COMPLETE, request);
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tSTATE END: " + request + " = " + false); //$NON-NLS-1$ //$NON-NLS-2$
}
fCompareRequestsInProgress.remove(request);
if (!request.isCanceled()) {
if (request.isEqual()) {
delta.setElement(request.getElement());
doRestore(delta, request.knowsHasChildren(), request.knowChildCount(), request.checkChildrenRealized());
} else if (request.getModelIndex() != -1) {
// Comparison failed.
// Check if the delta has a reveal flag, and if its index
// matches the index
// of the element that it was compared against. If this is the
// case,
// strip the reveal flag from the delta as it is most likely not
// applicable
// anymore.
if ((delta.getFlags() & IModelDelta.REVEAL) != 0 && delta.getIndex() == request.getModelIndex()) {
delta.setFlags(delta.getFlags() & ~IModelDelta.REVEAL);
}
}
}
}
/**
* Saves the viewer's state for the previous input. * @param oldInput
*/
protected void saveViewerState(Object input) {
IElementMementoProvider stateProvider = ViewerAdapterService.getMementoProvider(input);
if (stateProvider != null) {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("STATE SAVE BEGIN: " + input); //$NON-NLS-1$
}
// build a model delta representing expansion and selection state
final ModelDelta saveDeltaRoot = new ModelDelta(input, IModelDelta.NO_CHANGE);
buildViewerState(saveDeltaRoot);
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tSAVE DELTA FROM VIEW:\n" + saveDeltaRoot); //$NON-NLS-1$
}
if (fPendingState != null) {
// If the restore for the current input was never completed,
// preserve
// that restore along with the restore that was completed.
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tSAVE OUTSTANDING RESTORE: " + fPendingState); //$NON-NLS-1$
}
IModelDeltaVisitor pendingStateVisitor = new IModelDeltaVisitor() {
public boolean visit(IModelDelta pendingDeltaNode, int depth) {
// Ignore the top element.
if (pendingDeltaNode.getParentDelta() == null) {
return true;
}
// Find the node in the save delta which is the parent
// of to the current pending delta node.
// If the parent node cannot be found, it means that
// most likely the user collapsed the parent node before
// the children were ever expanded.
// If the pending state node already exists in the
// parent
// node, it is already processed and we can skip it.
// If the pending state node does not contain any flags,
// we can also skip it.
ModelDelta saveDeltaNode = findSaveDelta(saveDeltaRoot, pendingDeltaNode);
if (saveDeltaNode != null && !isDeltaInParent(pendingDeltaNode, saveDeltaNode)
&& pendingDeltaNode.getFlags() != IModelDelta.NO_CHANGE) {
// There should be only one delta element with
// the REVEAL flag in the entire save delta. The
// reveal flag in the pending delta trumps the one
// in the save delta because most likely the restore
// operation did not yet complete the reveal
// operation.
if ((pendingDeltaNode.getFlags() & IModelDelta.REVEAL) != 0) {
clearRevealFlag(saveDeltaRoot);
}
saveDeltaNode.setChildCount(pendingDeltaNode.getParentDelta().getChildCount());
copyIntoDelta(pendingDeltaNode, saveDeltaNode);
} else {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID
.equals(getPresentationContext().getId()))) {
System.out.println("\tSKIPPED: " + pendingDeltaNode.getElement()); //$NON-NLS-1$
}
}
// If the pending delta node has a memento element, its
// children should also be mementos therefore the copy
// delta operation should have added all the children
// of this pending delta node into the save delta.
if (pendingDeltaNode.getElement() instanceof IMemento) {
return false;
} else {
return pendingDeltaNode.getChildCount() > 0;
}
}
};
fPendingState.accept(pendingStateVisitor);
}
if (saveDeltaRoot.getChildDeltas().length > 0) {
// encode delta with mementos in place of elements, in non-UI
// thread
encodeDelta(saveDeltaRoot, stateProvider);
} else {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("STATE SAVE CANCELED, NO DATA"); //$NON-NLS-1$
}
}
}
}
private void clearRevealFlag(ModelDelta saveRootDelta) {
IModelDeltaVisitor clearDeltaVisitor = new IModelDeltaVisitor() {
public boolean visit(IModelDelta delta, int depth) {
if ((delta.getFlags() & IModelDelta.REVEAL) != 0) {
((ModelDelta) delta).setFlags(delta.getFlags() & ~IModelDelta.REVEAL);
}
return true;
}
};
saveRootDelta.accept(clearDeltaVisitor);
}
private ModelDelta findSaveDelta(ModelDelta saveDeltaRoot, IModelDelta pendingStateDelta) {
// Create a path of elements to the pendingStateDelta.
LinkedList deltaPath = new LinkedList();
IModelDelta delta = pendingStateDelta;
while (delta.getParentDelta() != null) {
delta = delta.getParentDelta();
deltaPath.addFirst(delta);
}
// For each element in the path of the pendingStateDelta, find the
// corresponding
// element in the partially restored delta being saved.
Iterator itr = deltaPath.iterator();
// Skip the root element
itr.next();
ModelDelta saveDelta = saveDeltaRoot;
outer: while (itr.hasNext()) {
IModelDelta itrDelta = (IModelDelta) itr.next();
for (int i = 0; i < saveDelta.getChildDeltas().length; i++) {
if (deltasEqual(saveDelta.getChildDeltas()[i], itrDelta)) {
saveDelta = (ModelDelta) saveDelta.getChildDeltas()[i];
continue outer;
}
}
return null;
}
return saveDelta;
}
private boolean deltasEqual(IModelDelta d1, IModelDelta d2) {
// Note: don't compare the child count, because it is
// incorrect for nodes which have not been expanded yet.
return d1.getElement().equals(d2.getElement()) && d1.getIndex() == d2.getIndex();
}
private boolean isDeltaInParent(IModelDelta delta, ModelDelta destParent) {
for (int i = 0; i < destParent.getChildDeltas().length; i++) {
if (deltasEqual(destParent.getChildDeltas()[i], delta)) {
return true;
}
}
return false;
}
private void copyIntoDelta(IModelDelta delta, ModelDelta destParent) {
// Search the destination and make sure that the same delta
// doesn't exist already.
ModelDelta newDelta = destParent.addNode(delta.getElement(), delta.getIndex(), delta.getFlags(), delta
.getChildCount());
for (int i = 0; i < delta.getChildDeltas().length; i++) {
copyIntoDelta(delta.getChildDeltas()[i], newDelta);
}
}
/**
* Encodes delta elements into mementos using the given provider.
*
* @param delta
* @param stateProvider
*/
protected void encodeDelta(final ModelDelta rootDelta, final IElementMementoProvider defaultProvider) {
final Object input = rootDelta.getElement();
final XMLMemento inputMemento = XMLMemento.createWriteRoot("VIEWER_INPUT_MEMENTO"); //$NON-NLS-1$
final XMLMemento childrenMemento = XMLMemento.createWriteRoot("CHILDREN_MEMENTO"); //$NON-NLS-1$
final IMementoManager manager = new IMementoManager() {
/**
* list of memento requests
*/
private List requests = new ArrayList();
private boolean abort = false;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .IMementoManager
* #requestComplete(org.eclipse.debug.internal.ui.viewers
* .model.provisional.IElementMementoRequest)
*/
public synchronized void requestComplete(IElementMementoRequest request) {
notifyStateUpdate(input, UPDATE_COMPLETE, request);
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tSTATE END: " + request); //$NON-NLS-1$
}
if (!abort) {
if (!request.isCanceled() && (request.getStatus() == null || request.getStatus().isOK())) {
requests.remove(request);
if (requests.isEmpty()) {
XMLMemento keyMemento = (XMLMemento) rootDelta.getElement();
StringWriter writer = new StringWriter();
try {
keyMemento.save(writer);
fViewerStates.put(writer.toString(), rootDelta);
} catch (IOException e) {
DebugUIPlugin.log(e);
}
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID
.equals(getPresentationContext().getId()))) {
System.out.println("STATE SAVE COMPLETED: " + rootDelta); //$NON-NLS-1$
}
stateSaveComplete(input, this);
}
} else {
abort = true;
Iterator iterator = requests.iterator();
while (iterator.hasNext()) {
IElementMementoRequest req = (IElementMementoRequest) iterator.next();
req.cancel();
}
requests.clear();
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext()
.getId()))) {
System.out.println("STATE SAVE ABORTED: " + rootDelta.getElement()); //$NON-NLS-1$
}
stateSaveComplete(input, this);
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .IMementoManager#processReqeusts()
*/
public synchronized void processReqeusts() {
Map providers = new HashMap();
Iterator iterator = requests.iterator();
while (iterator.hasNext()) {
IElementMementoRequest request = (IElementMementoRequest) iterator.next();
notifyStateUpdate(input, UPDATE_BEGINS, request);
IElementMementoProvider provider = ViewerAdapterService.getMementoProvider(request.getElement());
if (provider == null) {
provider = defaultProvider;
}
List reqs = (List) providers.get(provider);
if (reqs == null) {
reqs = new ArrayList();
providers.put(provider, reqs);
}
reqs.add(request);
}
iterator = providers.entrySet().iterator();
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
IElementMementoProvider provider = (IElementMementoProvider) entry.getKey();
List reqs = (List) entry.getValue();
provider.encodeElements((IElementMementoRequest[]) reqs.toArray(new IElementMementoRequest[reqs
.size()]));
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .IMementoManager
* #addRequest(org.eclipse.debug.internal.ui.viewers.
* model.provisional.IElementMementoRequest)
*/
public synchronized void addRequest(IElementMementoRequest request) {
requests.add(request);
}
};
IModelDeltaVisitor visitor = new IModelDeltaVisitor() {
public boolean visit(IModelDelta delta, int depth) {
// Add the CONTENT flag to all nodes with an EXPAND flag.
// During restoring, this flag is used as a marker indicating
// whether all the content of a given element has been
// retrieved.
if ((delta.getFlags() | IModelDelta.EXPAND) != 0) {
((ModelDelta)delta).setFlags(delta.getFlags() | IModelDelta.CONTENT);
}
// This is the root element, save the root element memento in 'inputMemento'.
if (delta.getParentDelta() == null) {
manager.addRequest(new ElementMementoRequest(ModelContentProvider.this, input, manager,
getPresentationContext(), delta.getElement(), getViewerTreePath(delta), inputMemento,
(ModelDelta) delta));
} else {
// If this is another node element, save the memento to a children memento.
if (!(delta.getElement() instanceof XMLMemento)) {
manager.addRequest(new ElementMementoRequest(ModelContentProvider.this, input, manager,
getPresentationContext(), delta.getElement(), getViewerTreePath(delta), childrenMemento
.createChild("CHILD_ELEMENT"), (ModelDelta) delta)); //$NON-NLS-1$
}
}
return true;
}
};
rootDelta.accept(visitor);
stateSaveStarted(input, manager);
manager.processReqeusts();
}
/**
* Called when a state save is starting.
*
* @param manager
*/
private synchronized void stateSaveStarted(Object input, IMementoManager manager) {
notifyStateUpdate(input, STATE_SAVE_SEQUENCE_BEGINS, null);
fPendingStateSaves.add(manager);
}
/**
* Called when a state save is complete.
*
* @param manager
*/
private synchronized void stateSaveComplete(Object input, IMementoManager manager) {
notifyStateUpdate(input, STATE_SAVE_SEQUENCE_COMPLETE, null);
fPendingStateSaves.remove(manager);
if (fQueuedRestore != null) {
Object temp = fQueuedRestore;
fQueuedRestore = null;
restoreViewerState(temp);
}
}
/**
* Returns whether any state saving is in progress.
*
* @return whether any state saving is in progress
*/
private synchronized boolean isSavingState() {
return !fPendingStateSaves.isEmpty();
}
/**
* Builds a delta with the given root delta for expansion/selection state.
*
* @param delta
* root delta
*/
protected abstract void buildViewerState(ModelDelta delta);
/**
* Uninstalls the model proxy installed for the given element, if any.
*
* @param element
*/
protected synchronized void disposeModelProxy(TreePath path) {
IModelProxy proxy = (IModelProxy) fTreeModelProxies.remove(path);
if (proxy != null) {
proxy.dispose();
}
proxy = (IModelProxy) fModelProxies.remove(path.getLastSegment());
if (proxy != null) {
proxy.dispose();
}
}
/**
* Uninstalls each model proxy
*/
protected synchronized void disposeAllModelProxies() {
Iterator updatePolicies = fModelProxies.values().iterator();
while (updatePolicies.hasNext()) {
IModelProxy proxy = (IModelProxy) updatePolicies.next();
proxy.dispose();
}
fModelProxies.clear();
updatePolicies = fTreeModelProxies.values().iterator();
while (updatePolicies.hasNext()) {
IModelProxy proxy = (IModelProxy) updatePolicies.next();
proxy.dispose();
}
fTreeModelProxies.clear();
}
protected synchronized IModelProxy[] getModelProxies() {
IModelProxy[] proxies = new IModelProxy[fTreeModelProxies.size() + fModelProxies.size()];
fTreeModelProxies.values().toArray(proxies);
System.arraycopy(fModelProxies.values().toArray(), 0, proxies, fModelProxies.size(), fModelProxies.size());
return proxies;
}
protected synchronized IModelProxy getElementProxy(TreePath path) {
while (path != null) {
IModelProxy proxy = (IModelProxy) fTreeModelProxies.get(path);
if (proxy != null) {
return proxy;
}
proxy = (IModelProxy) fModelProxies.get(path.getLastSegment());
if (proxy != null) {
return proxy;
}
path = path.getParentPath();
}
return null;
}
/**
* Installs the model proxy for the given element into this content provider
* if not already installed.
*
* @param element
* element to install an update policy for
*/
protected synchronized void installModelProxy(Object input, TreePath path) {
if (!fTreeModelProxies.containsKey(path) && !fModelProxies.containsKey(path.getLastSegment())) {
Object element = path.getSegmentCount() != 0 ? path.getLastSegment() : input;
IModelProxy proxy = null;
IModelProxyFactory2 modelProxyFactory2 = ViewerAdapterService.getModelProxyFactory2(element);
if (modelProxyFactory2 != null) {
proxy = modelProxyFactory2.createTreeModelProxy(input, path, getPresentationContext());
if (proxy != null) {
fTreeModelProxies.put(path, proxy);
}
}
if (proxy == null) {
IModelProxyFactory modelProxyFactory = ViewerAdapterService.getModelProxyFactory(element);
if (modelProxyFactory != null) {
proxy = modelProxyFactory.createModelProxy(element, getPresentationContext());
if (proxy != null) {
fModelProxies.put(element, proxy);
}
}
}
if (proxy != null) {
final IModelProxy finalProxy = proxy;
if (proxy != null) {
Job job = new Job("Model Proxy installed notification job") {//$NON-NLS-1$
protected IStatus run(IProgressMonitor monitor) {
if (!monitor.isCanceled()) {
IPresentationContext context = null;
Viewer viewer = null;
synchronized (ModelContentProvider.this) {
if (!isDisposed()) {
context = getPresentationContext();
viewer = (Viewer) getViewer();
}
}
if (context != null && !finalProxy.isDisposed()) {
finalProxy.init(context);
finalProxy.addModelChangedListener(ModelContentProvider.this);
finalProxy.installed(viewer);
}
}
return Status.OK_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#shouldRun()
*/
public boolean shouldRun() {
return !isDisposed();
}
};
job.setSystem(true);
job.schedule();
}
}
}
}
/**
* Returns the presentation context for this content provider.
*
* @return presentation context
*/
protected abstract IPresentationContext getPresentationContext();
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.provisional.IModelChangedListener
* #modelChanged
* (org.eclipse.debug.internal.ui.viewers.provisional.IModelDelta)
*/
public synchronized void modelChanged(final IModelDelta delta, final IModelProxy proxy) {
if (fViewer != null && !proxy.isDisposed()) {
WorkbenchJob job = new WorkbenchJob(fViewer.getDisplay(), "process model delta") { //$NON-NLS-1$
public IStatus runInUIThread(IProgressMonitor monitor) {
if (!proxy.isDisposed()) {
if (DEBUG_DELTAS
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext()
.getId()))) {
DebugUIPlugin.debug("RECEIVED DELTA: " + delta.toString()); //$NON-NLS-1$
}
updateModel(delta, getModelDeltaMask());
// Call model listeners after updating the viewer model.
Object[] listeners = fModelListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
((IModelChangedListener) listeners[i]).modelChanged(delta, proxy);
}
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
/**
* @see ITreeModelContentProvider#setModelDeltaMask(int)
*/
public void setModelDeltaMask(int mask) {
fModelDeltaMask = mask;
}
/**
* @see ITreeModelContentProvider#getModelDeltaMask()
*/
public int getModelDeltaMask() {
return fModelDeltaMask;
}
public void updateModel(IModelDelta delta, int mask) {
IModelDelta[] deltaArray = new IModelDelta[] { delta };
updateNodes(deltaArray, mask & (IModelDelta.REMOVED | IModelDelta.UNINSTALL));
updateNodes(deltaArray, mask & ITreeModelContentProvider.UPDATE_MODEL_DELTA_FLAGS
& ~(IModelDelta.REMOVED | IModelDelta.UNINSTALL));
updateNodes(deltaArray, mask & ITreeModelContentProvider.CONTROL_MODEL_DELTA_FLAGS);
}
/**
* Updates the viewer with the following deltas.
*
* @param nodes
* Model deltas to be processed.
* @param override
* If true, it overrides the mode which suppresses processing of
* SELECT, REVEAL, EXPAND, COLLAPSE flags of {@link IModelDelta}.
*/
protected void updateNodes(IModelDelta[] nodes, int mask) {
for (int i = 0; i < nodes.length; i++) {
IModelDelta node = nodes[i];
int flags = node.getFlags() & mask;
if ((flags & IModelDelta.ADDED) != 0) {
handleAdd(node);
}
if ((flags & IModelDelta.REMOVED) != 0) {
handleRemove(node);
}
if ((flags & IModelDelta.CONTENT) != 0) {
handleContent(node);
}
if ((flags & IModelDelta.STATE) != 0) {
handleState(node);
}
if ((flags & IModelDelta.INSERTED) != 0) {
handleInsert(node);
}
if ((flags & IModelDelta.REPLACED) != 0) {
handleReplace(node);
}
if ((flags & IModelDelta.INSTALL) != 0) {
handleInstall(node);
}
if ((flags & IModelDelta.UNINSTALL) != 0) {
handleUninstall(node);
}
if ((flags & IModelDelta.EXPAND) != 0) {
handleExpand(node);
}
if ((flags & IModelDelta.COLLAPSE) != 0) {
handleCollapse(node);
}
if ((flags & IModelDelta.SELECT) != 0) {
handleSelect(node);
}
if ((flags & IModelDelta.REVEAL) != 0) {
handleReveal(node);
}
updateNodes(node.getChildDeltas(), mask);
}
}
protected abstract void handleState(IModelDelta delta);
protected abstract void handleSelect(IModelDelta delta);
protected abstract void handleExpand(IModelDelta delta);
protected abstract void handleCollapse(IModelDelta delta);
protected abstract void handleContent(IModelDelta delta);
protected abstract void handleRemove(IModelDelta delta);
protected abstract void handleAdd(IModelDelta delta);
protected abstract void handleInsert(IModelDelta delta);
protected abstract void handleReplace(IModelDelta delta);
protected abstract void handleReveal(IModelDelta delta);
protected void handleInstall(IModelDelta delta) {
installModelProxy(getViewer().getInput(), getFullTreePath(delta));
}
protected void handleUninstall(IModelDelta delta) {
disposeModelProxy(getFullTreePath(delta));
}
/**
* Returns a tree path for the node including the root element.
*
* @param node
* model delta
* @return corresponding tree path
*/
protected TreePath getFullTreePath(IModelDelta node) {
ArrayList list = new ArrayList();
while (node.getParentDelta() != null) {
list.add(0, node.getElement());
node = node.getParentDelta();
}
return new TreePath(list.toArray());
}
/**
* Returns a tree path for the node, *not* including the root element.
*
* @param node
* model delta
* @return corresponding tree path
*/
protected TreePath getViewerTreePath(IModelDelta node) {
ArrayList list = new ArrayList();
IModelDelta parentDelta = node.getParentDelta();
while (parentDelta != null) {
list.add(0, node.getElement());
node = parentDelta;
parentDelta = node.getParentDelta();
}
return new TreePath(list.toArray());
}
/**
* Returns the viewer this content provider is working for.
*
* @return viewer
*/
protected ITreeModelContentProviderTarget getViewer() {
return fViewer;
}
/**
* Translates and returns the given child index from the viewer coordinate
* space to the model coordinate space.
*
* @param parentPath
* path to parent element
* @param index
* index of child element in viewer (filtered) space
* @return index of child element in model (raw) space
*/
public/* protected */int viewToModelIndex(TreePath parentPath, int index) {
return fTransform.viewToModelIndex(parentPath, index);
}
/**
* Translates and returns the given child count from the viewer coordinate
* space to the model coordinate space.
*
* @param parentPath
* path to parent element
* @param count
* number of child elements in viewer (filtered) space
* @return number of child elements in model (raw) space
*/
public/* protected */int viewToModelCount(TreePath parentPath, int count) {
return fTransform.viewToModelCount(parentPath, count);
}
/**
* Translates and returns the given child index from the model coordinate
* space to the viewer coordinate space.
*
* @param parentPath
* path to parent element
* @param index
* index of child element in model (raw) space
* @return index of child element in viewer (filtered) space or -1 if
* filtered
*/
public int modelToViewIndex(TreePath parentPath, int index) {
return fTransform.modelToViewIndex(parentPath, index);
}
/**
* Translates and returns the given child count from the model coordinate
* space to the viewer coordinate space.
*
* @param parentPath
* path to parent element
* @param count
* child count element in model (raw) space
* @return child count in viewer (filtered) space
*/
public int modelToViewChildCount(TreePath parentPath, int count) {
return fTransform.modelToViewCount(parentPath, count);
}
/**
* Notes that the child at the specified index of the given parent element
* has been filtered from the viewer. Returns whether the child at the given
* index was already filtered.
*
* @param parentPath
* path to parent element
* @param index
* index of child element to be filtered
* @param element
* the filtered element
* @return whether the child was already filtered
*/
protected boolean addFilteredIndex(TreePath parentPath, int index, Object element) {
return fTransform.addFilteredIndex(parentPath, index, element);
}
/**
* Notes that the element at the given index has been removed from its
* parent and filtered indexes should be updated accordingly.
*
* @param parentPath
* path to parent element
* @param index
* index of element that was removed
*/
protected void removeElementFromFilters(TreePath parentPath, int index) {
fTransform.removeElementFromFilters(parentPath, index);
}
/**
* Removes the given element from filtered elements of the given parent
* element. Return true if the element was removed, otherwise false.
*
* @param parentPath
* path to parent element
* @param element
* element to remove
* @return whether the element was removed
*/
protected boolean removeElementFromFilters(TreePath parentPath, Object element) {
return fTransform.removeElementFromFilters(parentPath, element);
}
/**
* The child count for a parent has been computed. Ensure any filtered items
* above the given count are cleared.
*
* @param parentPath
* path to parent element
* @param childCount
* number of children
*/
protected void setModelChildCount(TreePath parentPath, int childCount) {
fTransform.setModelChildCount(parentPath, childCount);
}
/**
* Returns whether the given element is filtered.
*
* @param parentElementOrTreePath
* the parent element or path
* @param element
* the child element
* @return whether to filter the element
*/
public boolean shouldFilter(Object parentElementOrTreePath, Object element) {
ViewerFilter[] filters = fViewer.getFilters();
if (filters.length > 0) {
for (int j = 0; j < filters.length; j++) {
if (!(filters[j].select((Viewer) fViewer, parentElementOrTreePath, element))) {
return true;
}
}
}
return false;
}
/**
* Returns whether the given index of the specified parent was previously
* filtered.
*
* @param parentPath
* @param index
* @return whether the element at the given index was filtered
*/
protected boolean isFiltered(TreePath parentPath, int index) {
return fTransform.isFiltered(parentPath, index);
}
/**
* Notification the given element is being unmapped.
*
* @param path
*/
public void unmapPath(TreePath path) {
// System.out.println("Unmap " + path.getLastSegment());
fTransform.clear(path);
cancelSubtreeUpdates(path);
}
/**
* Returns filtered children or <code>null</code>
*
* @param parent
* @return filtered children or <code>null</code>
*/
protected int[] getFilteredChildren(TreePath parent) {
return fTransform.getFilteredChildren(parent);
}
protected void clearFilteredChild(TreePath parent, int modelIndex) {
fTransform.clear(parent, modelIndex);
}
protected void clearFilters(TreePath parent) {
fTransform.clear(parent);
}
protected synchronized void checkIfRestoreComplete() {
if (fPendingState == null) {
return;
}
CheckState state = new CheckState();
fPendingState.accept(state);
if (state.isComplete()) {
if (DEBUG_STATE_SAVE_RESTORE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("STATE RESTORE COMPELTE: " + fPendingState); //$NON-NLS-1$
}
notifyStateUpdate(fPendingState.getElement(), STATE_RESTORE_SEQUENCE_COMPLETE, null);
fPendingState = null;
}
}
public void addViewerUpdateListener(IViewerUpdateListener listener) {
fUpdateListeners.add(listener);
}
public void removeViewerUpdateListener(IViewerUpdateListener listener) {
fUpdateListeners.remove(listener);
}
/**
* Notification an update request has started
*
* @param update
*/
void updateStarted(ViewerUpdateMonitor update) {
boolean begin = false;
synchronized (fRequestsInProgress) {
begin = fRequestsInProgress.isEmpty();
List requests = (List) fRequestsInProgress.get(update.getSchedulingPath());
if (requests == null) {
requests = new ArrayList();
fRequestsInProgress.put(update.getSchedulingPath(), requests);
}
requests.add(update);
}
if (begin) {
if (DEBUG_UPDATE_SEQUENCE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("MODEL SEQUENCE BEGINS"); //$NON-NLS-1$
}
notifyUpdate(UPDATE_SEQUENCE_BEGINS, null);
}
if (DEBUG_UPDATE_SEQUENCE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tBEGIN - " + update); //$NON-NLS-1$
}
notifyUpdate(UPDATE_BEGINS, update);
}
/**
* Notification an update request has completed
*
* @param update
*/
void updateComplete(ViewerUpdateMonitor update) {
boolean end = false;
synchronized (fRequestsInProgress) {
List requests = (List) fRequestsInProgress.get(update.getSchedulingPath());
if (requests != null) {
requests.remove(update);
trigger(update);
if (requests.isEmpty()) {
fRequestsInProgress.remove(update.getSchedulingPath());
}
}
end = fRequestsInProgress.isEmpty();
}
notifyUpdate(UPDATE_COMPLETE, update);
if (DEBUG_UPDATE_SEQUENCE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("\tEND - " + update); //$NON-NLS-1$
}
if (end) {
if (DEBUG_UPDATE_SEQUENCE
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID.equals(getPresentationContext().getId()))) {
System.out.println("MODEL SEQUENCE ENDS"); //$NON-NLS-1$
}
notifyUpdate(UPDATE_SEQUENCE_COMPLETE, null);
}
}
protected void notifyUpdate(final int type, final IViewerUpdate update) {
if (!fUpdateListeners.isEmpty()) {
Object[] listeners = fUpdateListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
final IViewerUpdateListener listener = (IViewerUpdateListener) listeners[i];
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
switch (type) {
case UPDATE_SEQUENCE_BEGINS:
listener.viewerUpdatesBegin();
break;
case UPDATE_SEQUENCE_COMPLETE:
listener.viewerUpdatesComplete();
break;
case UPDATE_BEGINS:
listener.updateStarted(update);
break;
case UPDATE_COMPLETE:
listener.updateComplete(update);
break;
}
}
public void handleException(Throwable exception) {
DebugUIPlugin.log(exception);
}
});
}
}
}
public void addStateUpdateListener(IStateUpdateListener listener) {
fStateUpdateListeners.add(listener);
}
public void removeStateUpdateListener(IStateUpdateListener listener) {
fStateUpdateListeners.remove(listener);
}
protected void notifyStateUpdate(final Object input, final int type, final IViewerUpdate update) {
if (!fStateUpdateListeners.isEmpty()) {
Object[] listeners = fStateUpdateListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
final IStateUpdateListener listener = (IStateUpdateListener) listeners[i];
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
switch (type) {
case STATE_SAVE_SEQUENCE_BEGINS:
listener.stateSaveUpdatesBegin(input);
break;
case STATE_SAVE_SEQUENCE_COMPLETE:
listener.stateSaveUpdatesComplete(input);
break;
case STATE_RESTORE_SEQUENCE_BEGINS:
listener.stateRestoreUpdatesBegin(input);
break;
case STATE_RESTORE_SEQUENCE_COMPLETE:
listener.stateRestoreUpdatesComplete(input);
break;
case UPDATE_BEGINS:
listener.stateUpdateStarted(input, update);
break;
case UPDATE_COMPLETE:
listener.stateUpdateComplete(input, update);
break;
}
}
public void handleException(Throwable exception) {
DebugUIPlugin.log(exception);
}
});
}
}
}
protected void cancelSubtreeUpdates(TreePath path) {
synchronized (fRequestsInProgress) {
Iterator iterator = fRequestsInProgress.entrySet().iterator();
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
TreePath entryPath = (TreePath) entry.getKey();
if (entryPath.startsWith(path, null)) {
List requests = (List) entry.getValue();
Iterator reqIter = requests.iterator();
while (reqIter.hasNext()) {
((IRequest) reqIter.next()).cancel();
}
}
}
List purge = new ArrayList();
iterator = fWaitingRequests.keySet().iterator();
while (iterator.hasNext()) {
TreePath entryPath = (TreePath) iterator.next();
if (entryPath.startsWith(path, null)) {
purge.add(entryPath);
}
}
iterator = purge.iterator();
while (iterator.hasNext()) {
fWaitingRequests.remove(iterator.next());
}
}
for (Iterator itr = fCompareRequestsInProgress.keySet().iterator(); itr.hasNext();) {
CompareRequestKey key = (CompareRequestKey) itr.next();
if (key.fPath.startsWith(path, null)) {
ElementCompareRequest compareRequest = (ElementCompareRequest) fCompareRequestsInProgress.get(key);
compareRequest.cancel();
itr.remove();
}
}
}
/**
* Returns whether this given request should be run, or should wait for
* parent update to complete.
*
* @param update
* @return whether to start the given request
*/
void schedule(ViewerUpdateMonitor update) {
synchronized (fRequestsInProgress) {
TreePath schedulingPath = update.getSchedulingPath();
List requests = (List) fWaitingRequests.get(schedulingPath);
if (requests == null) {
// no waiting requests
TreePath parentPath = schedulingPath;
while (fRequestsInProgress.get(parentPath) == null) {
parentPath = parentPath.getParentPath();
if (parentPath == null) {
// no running requests: start request
update.start();
return;
}
}
// request running on parent, add to waiting list
requests = new ArrayList();
requests.add(update);
fWaitingRequests.put(schedulingPath, requests);
} else {
// there are waiting requests: coalesce with existing request?
Iterator reqIter = requests.iterator();
while (reqIter.hasNext()) {
ViewerUpdateMonitor waiting = (ViewerUpdateMonitor) reqIter.next();
if (waiting.coalesce(update)) {
// coalesced with existing request, done
return;
}
}
// add to list of waiting requests
requests.add(update);
return;
}
}
}
protected boolean getElementChildrenRealized(TreePath path) {
synchronized (fRequestsInProgress) {
List requests = (List) fWaitingRequests.get(path);
if (requests != null) {
for (int i = 0; i < requests.size(); i++) {
if (requests.get(i) instanceof ChildrenUpdate) {
return false;
}
}
}
requests = (List) fRequestsInProgress.get(path);
if (requests != null) {
int numChildrenUpdateRequests = 0;
for (int i = 0; i < requests.size(); i++) {
if (requests.get(i) instanceof ChildrenUpdate) {
if (++numChildrenUpdateRequests > 1) {
return false;
}
}
}
}
}
return getViewer().getElementChildrenRealized(path);
}
/**
* Triggers waiting requests based on the given request that just completed.
*
* TODO: should we cancel child updates if a request has been canceled?
*
* @param request
*/
void trigger(ViewerUpdateMonitor request) {
if (fWaitingRequests.isEmpty()) {
return;
}
TreePath schedulingPath = request.getSchedulingPath();
List waiting = (List) fWaitingRequests.get(schedulingPath);
if (waiting == null) {
// no waiting, update the entry with the shortest path
int length = Integer.MAX_VALUE;
Iterator entries = fWaitingRequests.entrySet().iterator();
Entry candidate = null;
while (entries.hasNext()) {
Entry entry = (Entry) entries.next();
TreePath key = (TreePath) entry.getKey();
if (key.getSegmentCount() < length) {
candidate = entry;
length = key.getSegmentCount();
}
}
if (candidate != null) {
startHighestPriorityRequest((TreePath) candidate.getKey(), (List) candidate.getValue());
}
} else {
// start the highest priority request
startHighestPriorityRequest(schedulingPath, waiting);
}
}
/**
* @param key
* @param waiting
*/
private void startHighestPriorityRequest(TreePath key, List waiting) {
int priority = 4;
ViewerUpdateMonitor next = null;
Iterator requests = waiting.iterator();
while (requests.hasNext()) {
ViewerUpdateMonitor vu = (ViewerUpdateMonitor) requests.next();
if (vu.getPriority() < priority) {
next = vu;
priority = next.getPriority();
}
}
waiting.remove(next);
if (waiting.isEmpty()) {
fWaitingRequests.remove(key);
}
next.start();
}
/**
* Registers the given listener for model delta notification.
*
* @param listener
* model delta listener
*/
public void addModelChangedListener(IModelChangedListener listener) {
fModelListeners.add(listener);
}
/**
* Unregisters the given listener from model delta notification.
*
* @param listener
* model delta listener
*/
public void removeModelChangedListener(IModelChangedListener listener) {
fModelListeners.remove(listener);
}
/**
* Returns the element corresponding to the given tree path.
*
* @param path
* tree path
* @return model element
*/
protected Object getElement(TreePath path) {
if (path.getSegmentCount() > 0) {
return path.getLastSegment();
}
return getViewer().getInput();
}
/**
* Reschedule any children updates in progress for the given parent that
* have a start index greater than the given index. An element has been
* removed at this index, invalidating updates in progress.
*
* @param parentPath
* view tree path to parent element
* @param modelIndex
* index at which an element was removed
*/
protected void rescheduleUpdates(TreePath parentPath, int modelIndex) {
synchronized (fRequestsInProgress) {
List requests = (List) fRequestsInProgress.get(parentPath);
List reCreate = null;
if (requests != null) {
Iterator iterator = requests.iterator();
while (iterator.hasNext()) {
IViewerUpdate update = (IViewerUpdate) iterator.next();
if (update instanceof IChildrenUpdate) {
IChildrenUpdate childrenUpdate = (IChildrenUpdate) update;
if (childrenUpdate.getOffset() > modelIndex) {
childrenUpdate.cancel();
if (reCreate == null) {
reCreate = new ArrayList();
}
reCreate.add(childrenUpdate);
if (DEBUG_CONTENT_PROVIDER
&& getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID)) {
System.out.println("canceled update in progress handling REMOVE: " + childrenUpdate); //$NON-NLS-1$
}
}
}
}
}
requests = (List) fWaitingRequests.get(parentPath);
if (requests != null) {
Iterator iterator = requests.iterator();
while (iterator.hasNext()) {
IViewerUpdate update = (IViewerUpdate) iterator.next();
if (update instanceof IChildrenUpdate) {
IChildrenUpdate childrenUpdate = (IChildrenUpdate) update;
if (childrenUpdate.getOffset() > modelIndex) {
((ChildrenUpdate) childrenUpdate).setOffset(childrenUpdate.getOffset() - 1);
if (DEBUG_CONTENT_PROVIDER
&& (DEBUG_PRESENTATION_ID == null || DEBUG_PRESENTATION_ID
.equals(getPresentationContext().getId()))) {
System.out.println("modified waiting update handling REMOVE: " + childrenUpdate); //$NON-NLS-1$
}
}
}
}
}
// re-schedule canceled updates at new position.
// have to do this last else the requests would be waiting and
// get modified.
if (reCreate != null) {
Iterator iterator = reCreate.iterator();
while (iterator.hasNext()) {
IChildrenUpdate childrenUpdate = (IChildrenUpdate) iterator.next();
int start = childrenUpdate.getOffset() - 1;
int end = start + childrenUpdate.getLength();
for (int i = start; i < end; i++) {
((TreeModelContentProvider) this).doUpdateElement(parentPath, i);
}
}
}
}
}
}