blob: e951d65a4e886c38c42dd9230d1e9574a7a08db7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2016 Wind River Systems 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:
* Wind River Systems - initial API and implementation
* IBM Corporation - bug fixing
*******************************************************************************/
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.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
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.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
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.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.XMLMemento;
/**
* Class containing logic to save and restore expanded state of the tree model
* viewer.
* <p>
* When the input to the viewer is changes, the tree model viewer attempts to
* save the expansion state of elements as well as currently selected element and
* scroll position. Each expanded element is queried for its memento and all the
* collected mementos are saved into a delta tree then serialized by the viewer.<br>
* When a new input is set to the viewer, the viewer compares the input's memento
* with the stored mementos and if a match is found, it attempts to restore the
* previous expansion state to the viewer. As elements are revealed and realized
* in the viewer, the element's memento is compared against the memento stored in
* the saved state delta. If matching elements are found in the delta, the expansion
* and selection state is then restored to those elements.
* </p><p>
* Additionally to saving restoring state on input change, the viewer also
* saves/restores elements' state when the model requests viewer to refresh model
* structure. Since the viewer items are matched to the model elements using items'
* indexes, inserting or removing elements in model can cause the expansion state
* of elements to shift after a refresh. To compensate for this, the viewer saves
* the elements before a refresh is performed into a delta, but without encoding
* elements using mementos. As the refresh of the tree progresses, the save state
* is restored to the tree and elements are expanded or collapsed as needed to
* compensate for changes in model structure.
* </p>
* @see TreeModelContentProvider
*/
class ViewerStateTracker {
// State update type constants used in notifying listeners
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;
/**
* 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$
/**
* Collector of memento encoding requests.
*/
interface IElementMementoCollector {
/**
* Adds the request to this manager.
*
* @param request to add
*/
public void addRequest(ElementMementoRequest request);
/**
* Notification the request is complete.
*
* @param request that was completed
*/
public void requestComplete(ElementMementoRequest request);
/**
* Process the queued requests. Accepts no more new requests.
*/
public void processReqeusts();
/**
* Cancels the requests in progress.
*/
public void cancel();
}
/**
* LRU cache for viewer states
*/
class LRUMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
private int fMaxSize;
LRUMap(int maxSize) {
super();
fMaxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest) {
return size() > fMaxSize;
}
}
/**
* Content provider that is using this state tracker.
*/
private TreeModelContentProvider fContentProvider;
ViewerStateTracker(TreeModelContentProvider contentProvider) {
fContentProvider = contentProvider;
}
/**
* Map of viewer states keyed by viewer input mementos
*/
private Map<String, ModelDelta> fViewerStates = new LRUMap<String, ModelDelta>(20);
/**
* Pending viewer state to be restored
*/
private ModelDelta fPendingState = null;
/**
* Flag indicating that the content provider is performing
* state restore operations.
*/
private boolean fInStateRestore = false;
/**
* State update listeners
*/
private ListenerList<IStateUpdateListener> fStateUpdateListeners = new ListenerList<>();
/**
* Postpone restoring REVEAL element until the current updates are complete.
* See bug 324100
*/
protected PendingRevealDelta fPendingSetTopItem = null;
/**
* Set of IMementoManager's that are currently saving state
*/
private Set<IElementMementoCollector> fPendingStateSaves = new HashSet<IElementMementoCollector>();
/**
* Used to queue a viewer input for state restore
*/
private Object fQueuedRestore = null;
/**
* Object used to key compare requests in map.
*/
private static class CompareRequestKey {
CompareRequestKey(TreePath path, IModelDelta delta) {
fPath = path;
fDelta = delta;
}
TreePath fPath;
IModelDelta fDelta;
@Override
public boolean equals(Object obj) {
if (obj instanceof CompareRequestKey) {
CompareRequestKey key = (CompareRequestKey) obj;
return key.fDelta.equals(fDelta) && key.fPath.equals(fPath);
}
return false;
}
@Override
public int hashCode() {
return fDelta.hashCode() + fPath.hashCode();
}
}
/**
* Compare requests that are currently running.
*/
private Map<CompareRequestKey, ElementCompareRequest> fCompareRequestsInProgress = new LinkedHashMap<CompareRequestKey, ElementCompareRequest>();
/**
* Cancels pending updates.
*/
void dispose() {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
for (IElementMementoCollector emc : fPendingStateSaves) {
emc.cancel();
}
fStateUpdateListeners.clear();
for (ElementCompareRequest ecr : fCompareRequestsInProgress.values()) {
ecr.cancel();
}
fCompareRequestsInProgress.clear();
if (fPendingSetTopItem != null) {
fPendingSetTopItem.dispose();
}
}
/**
* Restores viewer state for the given input
*
* @param input
* viewer input
*/
private void startRestoreViewerState(final Object input) {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
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 IElementMementoCollector manager = new IElementMementoCollector() {
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)
*/
@Override
public void requestComplete(ElementMementoRequest request) {
if (fContentProvider.isDisposed()) {
return;
}
notifyStateUpdate(input, TreeModelContentProvider.UPDATE_COMPLETE, request);
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();
ModelDelta stateDelta = fViewerStates.get(keyMementoString);
if (stateDelta != null) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE INPUT COMARE ENDED : " + fRequest + " - MATCHING STATE FOUND"); //$NON-NLS-1$ //$NON-NLS-2$
}
// Process start of restore in an async cycle because we may still be inside inputChanged()
// call. I.e. the "input.equals(fContentProvider.getViewer().getInput())" test may fail.
fContentProvider.getViewer().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!fContentProvider.isDisposed() && input.equals(fContentProvider.getViewer().getInput())) {
ModelDelta stateDelta2 = fViewerStates.remove(keyMementoString);
if (stateDelta2 != null) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE BEGINS"); //$NON-NLS-1$
DebugUIPlugin.trace("\tRESTORE: " + stateDelta2.toString()); //$NON-NLS-1$
notifyStateUpdate(input, STATE_RESTORE_SEQUENCE_BEGINS, null);
}
stateDelta2.setElement(input);
fPendingState = stateDelta2;
doInitialRestore(fPendingState);
}
} else {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE CANCELED."); //$NON-NLS-1$
}
}
}
});
} else {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE INPUT COMARE ENDED : " + fRequest + " - NO MATCHING STATE"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
} 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()
*/
@Override
public void processReqeusts() {
notifyStateUpdate(input, STATE_RESTORE_SEQUENCE_BEGINS, null);
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE INPUT COMARE BEGIN : " + fRequest); //$NON-NLS-1$
}
notifyStateUpdate(input, TreeModelContentProvider.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)
*/
@Override
public void addRequest(ElementMementoRequest req) {
fRequest = req;
}
@Override
public void cancel() {
// not used
}
};
manager.addRequest(
new ElementMementoRequest(fContentProvider, fContentProvider.getViewer().getInput(), manager,
delta.getElement(), fContentProvider.getViewerTreePath(delta), inputMemento, delta));
manager.processReqeusts();
} else {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE: No input memento provider"); //$NON-NLS-1$
}
}
}
/**
* Appends the state of the given subtree to the current pending delta.
* @param path Path to subtree to restore.
*/
void appendToPendingStateDelta(final TreePath path) {
if (fContentProvider.getViewer() == null)
{
return; // Not initialized yet.
}
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE APPEND BEGIN: " + path.getLastSegment()); //$NON-NLS-1$
}
// build a model delta representing expansion and selection state
final ModelDelta appendDeltaRoot = new ModelDelta(fContentProvider.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);
}
if (!fContentProvider.getViewer().saveElementState(path, delta, IModelDelta.COLLAPSE | IModelDelta.EXPAND | IModelDelta.SELECT)) {
// Path to save the state was not found or there was no
// (expansion) state to save! Abort.
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE APPEND CANCEL: Element " + path.getLastSegment() + " not found."); //$NON-NLS-1$ //$NON-NLS-2$
}
return;
}
// 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() {
@Override
public boolean visit(IModelDelta d, int depth) {
if ((d.getFlags() & IModelDelta.EXPAND) != 0) {
((ModelDelta) d).setFlags(d.getFlags() | IModelDelta.CONTENT);
}
return true;
}
});
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tAPPEND OUTSTANDING RESTORE: " + fPendingState); //$NON-NLS-1$
}
// If the append delta is generated for a sub-tree, copy the pending delta
// attributes into the pending delta.
if (path.getSegmentCount() > 0) {
fPendingState.accept( new IModelDeltaVisitor() {
@Override
public boolean visit(IModelDelta pendingDeltaNode, int depth) {
TreePath pendingDeltaPath = fContentProvider.getViewerTreePath(pendingDeltaNode);
if (path.startsWith(pendingDeltaPath, null))
{
ModelDelta appendDelta = findDeltaForPath(appendDeltaRoot, pendingDeltaPath);
appendDelta.setFlags(pendingDeltaNode.getFlags());
appendDelta.setChildCount(pendingDeltaNode.getChildCount());
appendDelta.setIndex(pendingDeltaNode.getIndex());
return true;
}
return false;
}
});
}
// Copy the pending state into the new appended state.
fPendingState.accept( new IModelDeltaVisitor() {
@Override
public boolean visit(IModelDelta pendingDeltaNode, int depth) {
// Skip 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 = findSubDeltaParent(appendDeltaRoot, pendingDeltaNode);
if (saveDeltaNode != null &&
!isDeltaInParent(pendingDeltaNode, saveDeltaNode) &&
pendingDeltaNode.getFlags() != IModelDelta.NO_CHANGE)
{
saveDeltaNode.setChildCount(pendingDeltaNode.getParentDelta().getChildCount());
copyIntoDelta(pendingDeltaNode, saveDeltaNode);
} else {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\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;
}
}
});
}
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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE APPEND COMPLETE " + fPendingState); //$NON-NLS-1$
}
} else {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE APPEND CANCELED: No Data"); //$NON-NLS-1$
}
}
}
/**
* Saves the viewer's state for the previous input. * @param oldInput
* @param input the {@link ModelDelta} input
*/
protected void saveViewerState(Object input) {
for (Iterator<ElementCompareRequest> itr = fCompareRequestsInProgress.values().iterator(); itr.hasNext();) {
itr.next().cancel();
itr.remove();
}
IElementMementoProvider stateProvider = ViewerAdapterService.getMementoProvider(input);
if (stateProvider != null) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tSAVE DELTA FROM VIEW:\n" + saveDeltaRoot); //$NON-NLS-1$
}
// check if pending restore reveal
if (fPendingSetTopItem != null) {
// set back the pending reveal flag
ModelDelta revealDelta = fPendingSetTopItem.getDelta();
revealDelta.setFlags(revealDelta.getFlags() | IModelDelta.REVEAL);
fPendingSetTopItem.dispose();
ModelDelta saveDeltaNode = findSubDeltaParent(saveDeltaRoot, revealDelta);
if (saveDeltaNode != null) {
clearRevealFlag(saveDeltaRoot);
ModelDelta child = saveDeltaNode.getChildDelta(revealDelta.getElement(), revealDelta.getIndex());
if (child != null) {
child.setFlags(child.getFlags() | IModelDelta.REVEAL);
} else {
// the node should be added if not found
saveDeltaNode.setChildCount(revealDelta.getParentDelta().getChildCount());
copyIntoDelta(revealDelta, saveDeltaNode);
}
} else {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tSKIPPED: " + revealDelta.getElement()); //$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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tSAVE OUTSTANDING RESTORE: " + fPendingState); //$NON-NLS-1$
}
IModelDeltaVisitor pendingStateVisitor = new IModelDeltaVisitor() {
@Override
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 = findSubDeltaParent(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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE SAVE CANCELED, NO DATA"); //$NON-NLS-1$
}
}
}
}
private void clearRevealFlag(ModelDelta saveRootDelta) {
IModelDeltaVisitor clearDeltaVisitor = new IModelDeltaVisitor() {
@Override
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 findSubDeltaParent(ModelDelta destinationDeltaRoot, IModelDelta subDelta) {
// Create a path of elements to the sub-delta.
LinkedList<IModelDelta> deltaPath = new LinkedList<IModelDelta>();
IModelDelta delta = subDelta;
while (delta.getParentDelta() != null) {
delta = delta.getParentDelta();
deltaPath.addFirst(delta);
}
// For each element in the path of the sub-delta, find the corresponding
// element in the destination delta
Iterator<IModelDelta> itr = deltaPath.iterator();
// Skip the root element
itr.next();
ModelDelta saveDelta = destinationDeltaRoot;
while (saveDelta != null && itr.hasNext()) {
IModelDelta itrDelta = itr.next();
saveDelta = saveDelta.getChildDelta(itrDelta.getElement(), itrDelta.getIndex());
}
return saveDelta;
}
private ModelDelta findDeltaForPath(ModelDelta root, TreePath path) {
ModelDelta delta = root;
for (int i = 0; i < path.getSegmentCount(); i++) {
delta = delta.getChildDelta(path.getSegment(i));
if (delta == null) {
return null;
}
}
return delta;
}
private boolean isDeltaInParent(IModelDelta delta, ModelDelta destParent) {
return destParent.getChildDelta(delta.getElement(), delta.getIndex()) != null;
}
private void copyIntoDelta(IModelDelta delta, ModelDelta destParent) {
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 rootDelta the {@link ModelDelta} to encode
* @param defaultProvider the default provider to use when processing the given delta
*
*/
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 IElementMementoCollector manager = new IElementMementoCollector() {
/**
* list of memento fRequests
*/
private List<IElementMementoRequest> fRequests = new ArrayList<IElementMementoRequest>();
/**
* Flag indicating whether the encoding of delta has been canceled.
*/
private boolean fCanceled = false;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.model.provisional.viewers
* .IMementoManager
* #requestComplete(org.eclipse.debug.internal.ui.viewers
* .model.provisional.IElementMementoRequest)
*/
@Override
public void requestComplete(ElementMementoRequest request) {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
notifyStateUpdate(input, TreeModelContentProvider.UPDATE_COMPLETE, request);
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tSTATE END: " + request); //$NON-NLS-1$
}
if (!request.isCanceled() && (request.getStatus() == null || request.getStatus().isOK())) {
boolean requestsComplted = false;
if (!fCanceled) {
fRequests.remove(request);
requestsComplted = fRequests.isEmpty();
}
if (requestsComplted) {
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 (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE SAVE COMPLETED: " + rootDelta); //$NON-NLS-1$
}
stateSaveComplete(input, this);
}
} else {
cancel();
}
}
@Override
public void cancel() {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
if (fCanceled) {
return;
}
fCanceled = true;
for (IElementMementoRequest req : fRequests) {
req.cancel();
}
fRequests.clear();
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("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()
*/
@Override
public void processReqeusts() {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
Map<IElementMementoProvider, List<IElementMementoRequest>> providers = new HashMap<IElementMementoProvider, List<IElementMementoRequest>>();
for (IElementMementoRequest request : fRequests) {
notifyStateUpdate(input, TreeModelContentProvider.UPDATE_BEGINS, request);
IElementMementoProvider provider = ViewerAdapterService.getMementoProvider(request.getElement());
if (provider == null) {
provider = defaultProvider;
}
List<IElementMementoRequest> reqs = providers.get(provider);
if (reqs == null) {
reqs = new ArrayList<IElementMementoRequest>();
providers.put(provider, reqs);
}
reqs.add(request);
}
for (Entry<IElementMementoProvider, List<IElementMementoRequest>> entry : providers.entrySet()) {
IElementMementoProvider provider = entry.getKey();
List<IElementMementoRequest> reqs = entry.getValue();
provider.encodeElements(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)
*/
@Override
public void addRequest(ElementMementoRequest request) {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
fRequests.add(request);
}
};
IModelDeltaVisitor visitor = new IModelDeltaVisitor() {
@Override
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(fContentProvider, input, manager,
delta.getElement(), fContentProvider.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(fContentProvider, input, manager,
delta.getElement(), fContentProvider.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 input the {@link ModelDelta} input
* @param manager the manager to notify
*/
private void stateSaveStarted(Object input, IElementMementoCollector manager) {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
notifyStateUpdate(input, STATE_SAVE_SEQUENCE_BEGINS, null);
fPendingStateSaves.add(manager);
}
/**
* Called when a state save is complete.
* @param input the {@link ModelDelta} input
* @param manager the manager to notify
*/
private void stateSaveComplete(Object input, IElementMementoCollector manager) {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
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 boolean isSavingState() {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
return !fPendingStateSaves.isEmpty();
}
/**
* 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 void restoreViewerState(final Object input) {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
fPendingState = null;
if (isSavingState()) {
fQueuedRestore = input;
} else {
startRestoreViewerState(input);
}
}
public void cancelRestore(final TreePath path, final int flags) {
if (fInStateRestore) {
// If we are currently processing pending state already, ignore
// cancelRestore requests. These requests may be triggered in the viewer
// by changes to the tree state (Bug 295585).
return;
}
if ((flags & IModelDelta.REVEAL) != 0 && fPendingSetTopItem != null) {
fPendingSetTopItem.dispose();
return;
}
// Nothing else to do
if (fPendingState == null) {
return;
}
if ((flags & (IModelDelta.SELECT | IModelDelta.REVEAL)) != 0) {
// If we're canceling reveal and this is waiting for updates to complete
// then just cancel it and return
// If we're canceling select or reveal, cancel it for all of pending deltas
final int mask = flags & (IModelDelta.SELECT | IModelDelta.REVEAL);
fPendingState.accept(new IModelDeltaVisitor() {
@Override
public boolean visit(IModelDelta delta, int depth) {
int deltaFlags = delta.getFlags();
int newFlags = deltaFlags & ~mask;
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
if (deltaFlags != newFlags) {
DebugUIPlugin.trace("\tCANCEL: " + delta.getElement() + "(" + Integer.toHexString(deltaFlags & mask) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
((ModelDelta)delta).setFlags(newFlags);
return true;
}
});
}
if ((flags & ~(IModelDelta.SELECT | IModelDelta.REVEAL)) != 0) {
final int mask = flags & ~(IModelDelta.SELECT | IModelDelta.REVEAL);
// For other flags (EXPAND/COLLAPSE), cancel only from the matching path.
fPendingState.accept(new IModelDeltaVisitor() {
@Override
public boolean visit(IModelDelta delta, int depth) {
if (depth < path.getSegmentCount()) {
// Descend until we reach a matching depth.
TreePath deltaPath = fContentProvider.getViewerTreePath(delta);
if (path.startsWith(deltaPath, null)) {
return true;
} else {
return false;
}
}
else if (depth == path.getSegmentCount()) {
TreePath deltaPath = fContentProvider.getViewerTreePath(delta);
if (deltaPath.equals(path)) {
int deltaFlags = delta.getFlags();
int newFlags = deltaFlags & ~mask;
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
if (deltaFlags != newFlags) {
DebugUIPlugin.trace("\tCANCEL: " + delta.getElement() + "(" + Integer.toHexString(deltaFlags & mask) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
((ModelDelta)delta).setFlags(newFlags);
if ((flags & IModelDelta.EXPAND) != 0) {
// Descend delta to clear the EXPAND flags of a canceled expand
return true;
}
}
return false;
} else {
// We're clearing out flags of a matching sub-tree
// assert (flags & IModelDelta.EXPAND) != 0;
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
if (delta.getFlags() != IModelDelta.NO_CHANGE) {
DebugUIPlugin.trace("\tCANCEL: " + delta.getElement() + "(" + Integer.toHexString(delta.getFlags()) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
((ModelDelta)delta).setFlags(IModelDelta.NO_CHANGE);
return true;
}
}
});
}
}
/**
* Perform any restoration required for the given tree path.
* <p>
* This method is called after every viewer update completion to continue
* restoring the expansion state that was previously saved.
*
* @param path the tree path to update
* @param modelIndex the index in the current model
* @param knowsHasChildren if the content provider knows it has children already
* @param knowsChildCount if the content provider knows the current child count already
* @param checkChildrenRealized if any realized children should be checked or not
*/
void restorePendingStateOnUpdate(final TreePath path, final int modelIndex, final boolean knowsHasChildren,
final boolean knowsChildCount, final boolean checkChildrenRealized)
{
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
if (fPendingState == null) {
return;
}
IModelDeltaVisitor visitor = new IModelDeltaVisitor() {
@Override
public boolean visit(final IModelDelta delta, int depth) {
Object element = delta.getElement();
Object potentialMatch = depth != 0 ? path.getSegment(depth - 1) : fContentProvider.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(fContentProvider.getViewer().getInput());
}
if (provider != null) {
CompareRequestKey key = new CompareRequestKey(path, delta);
ElementCompareRequest existingRequest = 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(
fContentProvider, fContentProvider.getViewer().getInput(), potentialMatch, path,
(IMemento) element, (ModelDelta) delta, modelIndex, knowsHasChildren,
knowsChildCount, checkChildrenRealized);
fCompareRequestsInProgress.put(key, compareRequest);
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tSTATE BEGIN: " + compareRequest); //$NON-NLS-1$
}
notifyStateUpdate(element, TreeModelContentProvider.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.
restorePendingStateNode((ModelDelta) delta, knowsHasChildren, knowsChildCount, checkChildrenRealized);
}
return false;
}
// Only follow the paths that match the delta.
return element.equals(potentialMatch);
}
};
try {
fInStateRestore = true;
fPendingState.accept(visitor);
}
finally {
fInStateRestore = false;
}
checkIfRestoreComplete();
}
/**
* Checks whether restoring pending state is already complete.
*/
void checkIfRestoreComplete() {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
if (fPendingState == null) {
return;
}
/**
* Used to determine when restoration delta has been processed
*/
class CheckState implements IModelDeltaVisitor {
private boolean complete = true;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.internal.ui.viewers.provisional.IModelDeltaVisitor
* #visit(org.eclipse.debug.internal.ui.viewers.provisional.IModelDelta,
* int)
*/
@Override
public boolean visit(IModelDelta delta, int depth) {
// Filter 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 = fContentProvider.getViewerTreePath(delta);
if ( !fContentProvider.areElementUpdatesPending(deltaPath) &&
(!(delta.getElement() instanceof IMemento) || !areMementoUpdatesPending(delta)) )
{
removeDelta(delta);
return false;
}
}
if (flags != IModelDelta.REVEAL || (delta.getElement() instanceof IMemento)) {
complete = false;
return false;
}
}
return true;
}
public boolean isComplete() {
return complete;
}
private boolean areMementoUpdatesPending(IModelDelta delta) {
for (CompareRequestKey key : fCompareRequestsInProgress.keySet()) {
if (delta.getElement().equals(key.fDelta.getElement())) {
return true;
}
}
return false;
}
private void removeDelta(IModelDelta delta) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tRESTORE REMOVED: " + delta.getElement()); //$NON-NLS-1$
}
delta.accept(new IModelDeltaVisitor() {
@Override
public boolean visit(IModelDelta _visitorDelta, int depth) {
ModelDelta visitorDelta = (ModelDelta) _visitorDelta;
visitorDelta.setElement(ELEMENT_REMOVED);
visitorDelta.setFlags(IModelDelta.NO_CHANGE);
return true;
}
});
}
}
CheckState state = new CheckState();
fPendingState.accept(state);
if (state.isComplete()) {
// notify restore complete if REVEAL was restored also, otherwise
// postpone until then.
if (fPendingSetTopItem == null) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE COMPELTE: " + fPendingState); //$NON-NLS-1$
}
notifyStateUpdate(fPendingState.getElement(), STATE_RESTORE_SEQUENCE_COMPLETE, null);
}
fPendingState = null;
}
}
/**
* Restores the pending state in the given delta node. This method is called
* once the state tracker has found the element which matches the element in
* the given delta node.
* @param delta the {@link ModelDelta} to restore from
* @param knowsHasChildren if the content provider has computed its children
* @param knowsChildCount if the content provider has already computed the child count
* @param checkChildrenRealized if any realized children should be checked
*/
void restorePendingStateNode(final ModelDelta delta, boolean knowsHasChildren, boolean knowsChildCount, boolean checkChildrenRealized) {
final TreePath treePath = fContentProvider.getViewerTreePath(delta);
final IInternalTreeModelViewer viewer = fContentProvider.getViewer();
// Attempt to expand the node only if the children are known.
if (knowsHasChildren) {
if ((delta.getFlags() & IModelDelta.EXPAND) != 0) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tRESTORE EXPAND: " + treePath.getLastSegment()); //$NON-NLS-1$
}
viewer.expandToLevel(treePath, 1);
delta.setFlags(delta.getFlags() & ~IModelDelta.EXPAND);
}
if ((delta.getFlags() & IModelDelta.COLLAPSE) != 0) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tRESTORE COLLAPSE: " + treePath.getLastSegment()); //$NON-NLS-1$
}
// Check auto-expand before collapsing an element (bug 335734)
int autoexpand = fContentProvider.getViewer().getAutoExpandLevel();
if (autoexpand != ITreeModelViewer.ALL_LEVELS && autoexpand < (treePath.getSegmentCount() + 1)) {
fContentProvider.getViewer().setExpandedState(treePath, false);
}
delta.setFlags(delta.getFlags() & ~IModelDelta.COLLAPSE);
}
}
if ((delta.getFlags() & IModelDelta.SELECT) != 0) {
delta.setFlags(delta.getFlags() & ~IModelDelta.SELECT);
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tRESTORE SELECT: " + treePath.getLastSegment()); //$NON-NLS-1$
}
ITreeSelection currentSelection = (ITreeSelection)viewer.getSelection();
if (currentSelection == null || currentSelection.isEmpty()) {
viewer.setSelection(new TreeSelection(treePath), false, false);
} else {
TreePath[] currentPaths = currentSelection.getPaths();
boolean pathInSelection = false;
for (int i = 0; i < currentPaths.length; i++) {
if (currentPaths[i].equals(treePath)) {
pathInSelection = true;
break;
}
}
// Only set the selection if the element is not yet in
// selection. Otherwise the setSelection() call will
// update selection listeners needlessly.
if (!pathInSelection) {
TreePath[] newPaths = new TreePath[currentPaths.length + 1];
System.arraycopy(currentPaths, 0, newPaths, 0, currentPaths.length);
newPaths[newPaths.length - 1] = treePath;
viewer.setSelection(new TreeSelection(newPaths), false, false);
}
}
}
if ((delta.getFlags() & IModelDelta.REVEAL) != 0) {
delta.setFlags(delta.getFlags() & ~IModelDelta.REVEAL);
// Look for the reveal flag in the child deltas. If
// A child delta has the reveal flag, do not set the
// top element yet.
boolean setTopItem = true;
IModelDelta[] childDeltas = delta.getChildDeltas();
for (int i = 0; i < childDeltas.length; i++) {
IModelDelta childDelta = childDeltas[i];
int modelIndex = childDelta.getIndex();
if (modelIndex >= 0 && (childDelta.getFlags() & IModelDelta.REVEAL) != 0) {
setTopItem = false;
}
}
if (setTopItem) {
Assert.isTrue(fPendingSetTopItem == null);
fPendingSetTopItem = new PendingRevealDelta(treePath, delta);
viewer.addViewerUpdateListener(fPendingSetTopItem);
}
}
// If we know the child count of the element, look for the reveal
// flag in the child deltas. For the children with reveal flag start
// a new update.
// If the child delta's index is out of range, strip the reveal flag
// since it is no longer applicable.
if (knowsChildCount) {
int childCount = viewer.getChildCount(treePath);
if (childCount >= 0) {
ModelDelta[] childDeltas = (ModelDelta[])delta.getChildDeltas();
for (int i = 0; i < childDeltas.length; i++) {
ModelDelta childDelta = childDeltas[i];
int modelIndex = childDelta.getIndex();
if (modelIndex >= 0 && (childDelta.getFlags() & IModelDelta.REVEAL) != 0) {
if (modelIndex < childCount) {
fContentProvider.doUpdateElement(treePath, modelIndex);
} else {
childDelta.setFlags(childDelta.getFlags() & ~IModelDelta.REVEAL);
}
}
}
}
}
// Some children of this element were just updated. If all its
// children are now realized, clear out any elements that still
// have flags, because they represent elements that were removed.
if ((checkChildrenRealized &&
!fContentProvider.areChildrenUpdatesPending(treePath) &&
fContentProvider.getViewer().getElementChildrenRealized(treePath)) ||
(knowsHasChildren && !viewer.getHasChildren(treePath)) )
{
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tRESTORE CONTENT: " + treePath.getLastSegment()); //$NON-NLS-1$
}
delta.setFlags(delta.getFlags() & ~IModelDelta.CONTENT);
}
}
/**
* Utility that reveals the saved top item in the viewer. It listens for
* all content updates to complete in order to avoid having the desired top item
* scroll out as view content is filled in.
* <br>
* Revealing some elements can trigger expanding some of elements
* that have been just revealed. Therefore, we have to check one
* more time after the new triggered updates are completed if we
* have to set again the top index
*/
private class PendingRevealDelta implements IViewerUpdateListener {
private final TreePath fPathToReveal;
private final ModelDelta fRevealDelta;
PendingRevealDelta(TreePath pathToReveal, ModelDelta revealDelta) {
fPathToReveal = pathToReveal;
fRevealDelta = revealDelta;
}
/**
* Counter that tracks how many time the viewer updates were completed.
*/
private int fCounter = 0;
private Object fModelInput = fPendingState.getElement();
@Override
public void viewerUpdatesComplete() {
Assert.isTrue( fContentProvider.getViewer().getDisplay().getThread() == Thread.currentThread() );
IInternalTreeModelViewer viewer = fContentProvider.getViewer();
if (viewer == null || fPendingSetTopItem != this) {
return;
}
TreePath topPath = viewer.getTopElementPath();
if (!fPathToReveal.equals(topPath)) {
TreePath parentPath = fPathToReveal.getParentPath();
int index = viewer.findElementIndex(parentPath, fPathToReveal.getLastSegment());
if (index >= 0) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tRESTORE REVEAL: " + fPathToReveal.getLastSegment()); //$NON-NLS-1$
}
viewer.reveal(parentPath, index);
}
}
fCounter++;
// in case the pending state was already set to null, we assume that
// all others elements are restored, so we don't expect that REVEAL will
// trigger other updates
if (fCounter > 1 || fPendingState == null) {
dispose();
}
}
@Override
public void viewerUpdatesBegin() {}
@Override
public void updateStarted(IViewerUpdate update) {}
@Override
public void updateComplete(IViewerUpdate update) {}
/**
* Returns delta that is used to reveal the item.
* @return delta to be revealed.
*/
public ModelDelta getDelta() {
return fRevealDelta;
}
/**
* Resets the item
*/
public void dispose() {
// top item is set
fPendingSetTopItem = null;
IInternalTreeModelViewer viewer = fContentProvider.getViewer();
if (viewer == null) {
return;
}
// remove myself as viewer update listener
viewer.removeViewerUpdateListener(this);
if (fPendingState == null) {
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("STATE RESTORE COMPELTE: " + fPendingState); //$NON-NLS-1$
}
notifyStateUpdate(fModelInput, STATE_RESTORE_SEQUENCE_COMPLETE, null);
} else {
checkIfRestoreComplete();
}
}
}
/**
* Restore selection/expansion based on items already in the viewer
* @param delta the {@link ModelDelta} to restore from
*/
protected void doInitialRestore(ModelDelta delta) {
// Find the reveal delta and mark nodes on its path
// to reveal as elements are updated.
markRevealDelta(delta);
// Restore visible items.
// Note (Pawel Piech): the initial list of items is normally
// empty, so in most cases the code below does not do anything.
// Instead doRestore() is called when various updates complete.
int count = fContentProvider.getViewer().getChildCount(TreePath.EMPTY);
for (int i = 0; i < count; i++) {
Object data = fContentProvider.getViewer().getChildElement(TreePath.EMPTY, i);
if (data != null) {
restorePendingStateOnUpdate(new TreePath(new Object[]{data}), i, false, false, false);
}
}
}
/**
* Finds the delta with the reveal flag, then it walks up this
* delta and marks all the parents of it with the reveal flag.
* These flags are then used by the restore logic to restore
* and reveal all the nodes leading up to the element that should
* be ultimately at the top.
* @param rootDelta Delta to search
* @return The node just under the rootDelta which contains
* the reveal flag. <code>null</code> if no reveal flag was found.
*/
private ModelDelta markRevealDelta(ModelDelta rootDelta) {
final ModelDelta[] revealDelta = new ModelDelta[1];
IModelDeltaVisitor visitor = new IModelDeltaVisitor() {
@Override
public boolean visit(IModelDelta delta, int depth) {
if ( (delta.getFlags() & IModelDelta.REVEAL) != 0) {
revealDelta[0] = (ModelDelta)delta;
}
// Keep recursing only if we haven't found our delta yet.
return revealDelta[0] == null;
}
};
rootDelta.accept(visitor);
if (revealDelta[0] != null) {
ModelDelta parentDelta = (ModelDelta)revealDelta[0].getParentDelta();
while(parentDelta.getParentDelta() != null) {
revealDelta[0] = parentDelta;
revealDelta[0].setFlags(revealDelta[0].getFlags() | IModelDelta.REVEAL);
parentDelta = (ModelDelta)parentDelta.getParentDelta();
}
}
return revealDelta[0];
}
/**
* Builds a delta with the given root delta for expansion/selection state.
*
* @param delta
* root delta
*/
private void buildViewerState(ModelDelta delta) {
IInternalTreeModelViewer viewer = fContentProvider.getViewer();
viewer.saveElementState(TreeModelContentProvider.EMPTY_TREE_PATH, delta, IModelDelta.SELECT | IModelDelta.EXPAND);
// Add memento for top item if it is mapped to an element. The reveal memento
// is in its own path to avoid requesting unnecessary data when restoring it.
TreePath topElementPath = viewer.getTopElementPath();
if (topElementPath != null) {
ModelDelta parentDelta = delta;
TreePath parentPath = TreeModelContentProvider.EMPTY_TREE_PATH;
for (int i = 0; i < topElementPath.getSegmentCount(); i++) {
Object element = topElementPath.getSegment(i);
int index = viewer.findElementIndex(parentPath, element);
ModelDelta childDelta = parentDelta.getChildDelta(element);
if (childDelta == null) {
parentDelta = parentDelta.addNode(element, index, IModelDelta.NO_CHANGE);
} else {
parentDelta = childDelta;
}
parentPath = parentPath.createChildPath(element);
}
parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.REVEAL);
}
}
/**
* Cancels any outstanding compare requests for given element and its children.
* @param path Path of element to cancel updates for.
*/
void cancelStateSubtreeUpdates(TreePath path) {
for (Iterator<CompareRequestKey> itr = fCompareRequestsInProgress.keySet().iterator(); itr.hasNext();) {
CompareRequestKey key = itr.next();
if (key.fPath.startsWith(path, null)) {
ElementCompareRequest compareRequest = fCompareRequestsInProgress.get(key);
compareRequest.cancel();
itr.remove();
}
}
}
void compareFinished(ElementCompareRequest request, ModelDelta delta) {
notifyStateUpdate(request.getViewerInput(), TreeModelContentProvider.UPDATE_COMPLETE, request);
if (DebugUIPlugin.DEBUG_STATE_SAVE_RESTORE && DebugUIPlugin.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext())) {
DebugUIPlugin.trace("\tSTATE END: " + request + " = " + false); //$NON-NLS-1$ //$NON-NLS-2$
}
fCompareRequestsInProgress.remove(new CompareRequestKey(request.getElementPath(), delta));
if (!request.isCanceled()) {
if (request.isEqual()) {
delta.setElement(request.getElement());
restorePendingStateNode(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);
}
}
}
checkIfRestoreComplete();
}
void addStateUpdateListener(IStateUpdateListener listener) {
fStateUpdateListeners.add(listener);
}
void removeStateUpdateListener(IStateUpdateListener listener) {
fStateUpdateListeners.remove(listener);
}
void notifyStateUpdate(final Object input, final int type, final IViewerUpdate update) {
if (!fStateUpdateListeners.isEmpty()) {
for (IStateUpdateListener iStateUpdateListener : fStateUpdateListeners) {
final IStateUpdateListener listener = iStateUpdateListener;
SafeRunner.run(new ISafeRunnable() {
@Override
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 TreeModelContentProvider.UPDATE_BEGINS:
listener.stateUpdateStarted(input, update);
break;
case TreeModelContentProvider.UPDATE_COMPLETE:
listener.stateUpdateComplete(input, update);
break;
default:
break;
}
}
@Override
public void handleException(Throwable exception) {
DebugUIPlugin.log(exception);
}
});
}
}
}
}