blob: 2894d83b327e44955cf0c1e7b6a04c5d5edc3137 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 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
*******************************************************************************/
package org.eclipse.cdt.dsf.ui.viewmodel.update;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.cdt.dsf.internal.LoggingUtils;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerCountingRequestMonitor;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerDataRequestMonitor;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMAdapter;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMModelProxy;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.VMChildrenCountUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.VMChildrenUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.VMHasChildrenUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IElementPropertiesProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.PropertiesUpdateStatus;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.VMPropertiesUpdate;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
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.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
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.TreePath;
/**
* Base implementation of a caching view model provider.
*
* @since 1.0
*/
public class AbstractCachingVMProvider extends AbstractVMProvider
implements ICachingVMProvider, IElementPropertiesProvider, ICachingVMProviderExtension2
{
/**
* @since 2.0
*/
private final static String PROP_UPDATE_STATUS = "org.eclipse.cdt.dsf.ui.viewmodel.update.update_status"; //$NON-NLS-1$
/**
* @since 2.0
*/
private final static int LENGTH_PROP_IS_CHANGED_PREFIX = PROP_IS_CHANGED_PREFIX.length();
private boolean fDelayEventHandleForViewUpdate = false;
// debug flag
static boolean DEBUG_CACHE = false;
static {
DEBUG_CACHE = DsfUIPlugin.DEBUG && Boolean.parseBoolean(
Platform.getDebugOption("org.eclipse.cdt.dsf.ui/debug/vm/cache")); //$NON-NLS-1$
}
private static final int MAX_CACHE_SIZE = 1000;
/**
* Class representing a key to an element's data in the cache. The main
* components of this key are the viewer input and the path, they uniquely
* identify an element. The root element is used to track when a given
* root element is no longer in the cache and can therefore be disposed.
* The node is needed because different nodes have different lists of
* children for the same parent element.
*/
private static class ElementDataKey {
ElementDataKey(Object rootElement, IVMNode node, Object viewerInput, TreePath path) {
fRootElement = rootElement;
fNode = node;
fViewerInput = viewerInput;
fPath = path;
}
final Object fRootElement;
final IVMNode fNode;
final Object fViewerInput;
final TreePath fPath;
@Override
public String toString() {
return fNode.toString() + " " + //$NON-NLS-1$
(fPath.getSegmentCount() == 0 ? fViewerInput.toString() : fPath.getLastSegment().toString());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ElementDataKey)) return false;
ElementDataKey key = (ElementDataKey)obj;
return
(fNode == null && key.fNode == null || (fNode != null && fNode.equals(key.fNode))) &&
(fRootElement == null && key.fRootElement == null || (fRootElement != null && fRootElement.equals(key.fRootElement))) &&
(fViewerInput == null && key.fViewerInput == null || (fViewerInput != null && fViewerInput.equals(key.fViewerInput))) &&
(fPath == null && key.fPath == null || (fPath != null && fPath.equals(key.fPath)));
}
@Override
public int hashCode() {
return
(fRootElement != null ? fRootElement.hashCode() : 0) +
(fNode != null ? fNode.hashCode() : 0) +
(fViewerInput != null ? fViewerInput.hashCode() : 0) +
(fPath != null ? fPath.hashCode() : 0);
}
}
/**
* A base class for the entry in the cache. Since the cache maintains
* a double-linked list through all the entries, the linked list references
* are maintained in this class.
*/
private static class Entry {
final Object fKey;
Entry fNext;
Entry fPrevious;
Entry(Object key) {
fKey = key;
}
void insert(Entry nextEntry) {
fNext = nextEntry;
fPrevious = nextEntry.fPrevious;
fPrevious.fNext = this;
fNext.fPrevious = this;
}
void remove() {
fPrevious.fNext = fNext;
fNext.fPrevious = fPrevious;
}
void reinsert(Entry nextEntry) {
fPrevious.fNext = fNext;
fNext.fPrevious = fPrevious;
fNext = nextEntry;
fPrevious = nextEntry.fPrevious;
fPrevious.fNext = this;
fNext.fPrevious = this;
}
}
/**
* Entry with cached element data.
*/
private static class ElementDataEntry extends Entry implements ICacheEntry {
ElementDataEntry(ElementDataKey key) {
super(key);
}
/**
* Counter of flush operations performed on this entry. It is used
* by caching update operations to make sure that an update which
* was issued for a given entry is still valid for that entry when
* it is completed by the node.
*/
int fFlushCounter = 0;
/**
* Indicates that the data in this cache entry is out of date with
* the data on the target.
*/
Boolean fDirty = false;
/**
* Cached {@link IHasChildrenUpdate} result.
*/
Boolean fHasChildren = null;
/**
* Cached {@link IChildrenCountUpdate} result.
*/
Integer fChildrenCount = null;
/**
* Flag indicating that all the children of the given element are
* already cached.
*/
boolean fAllChildrenKnown = false;
/**
* Map containing children of this element, keyed by child index.
*/
Map<Integer,Object> fChildren = null;
/**
* Map containing element properties.
*
* @since 2.0
*/
Map<String, Object> fProperties = null;
/**
* Previous known element properties.
*
* @since 2.0
*/
Map<String, Object> fArchiveProperties = null;
/**
* Ensure this cache entry has a map in which to hold the children
* elements. If it doesn't, create one and give it an initial capacity
* of 30% more than the number of children we know the parent currently
* has (give it some room to grow). If we don't know the child count,
* give the map some nominal initial capacity.
*/
void ensureChildrenMap() {
if (fChildren == null) {
Integer childrenCount = fChildrenCount;
childrenCount = childrenCount != null ? childrenCount : 0;
int capacity = Math.max((childrenCount.intValue() * 4)/3, 32);
fChildren = new HashMap<Integer,Object>(capacity);
}
}
@Override
public String toString() {
return fKey.toString() + " = " + //$NON-NLS-1$
"[hasChildren=" + fHasChildren + ", " +//$NON-NLS-1$ //$NON-NLS-2$
"childrenCount=" + fChildrenCount + //$NON-NLS-1$
", children=" + fChildren + //$NON-NLS-1$
", properties=" + fProperties + //$NON-NLS-1$
", oldProperties=" + fArchiveProperties + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public IVMNode getNode() { return ((ElementDataKey)fKey).fNode; }
@Override
public Object getViewerInput() { return ((ElementDataKey)fKey).fViewerInput; }
@Override
public TreePath getElementPath() { return ((ElementDataKey)fKey).fPath; }
@Override
public boolean isDirty() { return fDirty; }
@Override
public Boolean getHasChildren() { return fHasChildren; }
@Override
public Integer getChildCount() { return fChildrenCount; }
@Override
public Map<Integer, Object> getChildren() { return fChildren; }
@Override
public Map<String, Object> getProperties() { return fProperties; }
@Override
public java.util.Map<String,Object> getArchiveProperties() { return fArchiveProperties; }
}
/**
* A key for a special marker entry in the cache. This marker entry is used
* to optimize repeated flushing of the cache.
* @see AbstractCachingVMProvider#flush(List)
*/
private static class FlushMarkerKey {
private Object fRootElement;
private IElementUpdateTester fElementTester;
FlushMarkerKey(Object rootElement, IElementUpdateTester pathTester) {
fRootElement = rootElement;
fElementTester = pathTester;
}
boolean includes(FlushMarkerKey key) {
return fRootElement.equals(key.fRootElement) &&
fElementTester.includes(key.fElementTester);
}
int getUpdateFlags(ElementDataKey key) {
if (fRootElement.equals(key.fRootElement)) {
return fElementTester.getUpdateFlags(key.fViewerInput, key.fPath);
}
return 0;
}
Collection<String> getPropertiesToFlush(ElementDataKey key, boolean isDirty) {
if (fRootElement.equals(key.fRootElement) && fElementTester instanceof IElementUpdateTesterExtension) {
return ((IElementUpdateTesterExtension)fElementTester).
getPropertiesToFlush(key.fViewerInput, key.fPath, isDirty);
}
return null;
}
@Override
public String toString() {
return fElementTester.toString() + " " + fRootElement.toString(); //$NON-NLS-1$
}
}
/**
* Marker used to keep track of whether any entries with the given
* root element are present in the cache.
*/
private static class RootElementMarkerKey {
private Object fRootElement;
RootElementMarkerKey(Object rootElement) {
fRootElement = rootElement;
}
@Override
public boolean equals(Object obj) {
return obj instanceof RootElementMarkerKey && ((RootElementMarkerKey)obj).fRootElement.equals(fRootElement);
}
@Override
public int hashCode() {
return fRootElement.hashCode();
}
@Override
public String toString() {
return fRootElement.toString();
}
}
class RootElementMarkerEntry extends Entry {
RootElementMarkerEntry(RootElementMarkerKey key) {
super(key);
}
@Override
void remove() {
super.remove();
rootElementRemovedFromCache(((RootElementMarkerKey)fKey).fRootElement);
}
@Override
public String toString() {
return "ROOT MARKER " + fKey; //$NON-NLS-1$
}
}
protected static String SELECTED_UPDATE_MODE = "org.eclipse.cdt.dsf.ui.viewmodel.update.selectedUpdateMode"; //$NON-NLS-1$
/**
* @since 1.1
*/
protected static String SELECTED_UPDATE_SCOPE = "org.eclipse.cdt.dsf.ui.viewmodel.update.selectedUpdateScope"; //$NON-NLS-1$
private IVMUpdatePolicy[] fAvailableUpdatePolicies;
public Map<Object, RootElementMarkerKey> fRootMarkers = new HashMap<Object, RootElementMarkerKey>();
/**
* Hash map holding cache data. To store the cache information, the cache uses a
* combination of this hash map and a double-linked list running through all
* the entries in the cache. The linked list is used to organize the cache entries
* in least recently used (LRU) order. This ordering is then used to delete least
* recently used entries in the cache and keep the cache from growing indefinitely.
* Also, the ordering is used to optimize the flushing of the cache data (see
* {@link FlushMarkerKey} for more details).
*/
private final Map<Object, Entry> fCacheData = Collections.synchronizedMap(new HashMap<Object, Entry>(200, 0.75f));
/**
* Pointer to the first cache entry in the double-linked list of cache entries.
*/
private final Entry fCacheListHead;
public AbstractCachingVMProvider(AbstractVMAdapter adapter, IPresentationContext presentationContext) {
super(adapter, presentationContext);
fCacheListHead = new Entry(null) {
@Override
public String toString() {
return "HEAD"; //$NON-NLS-1$
}
};
fCacheListHead.fNext = fCacheListHead;
fCacheListHead.fPrevious = fCacheListHead;
fAvailableUpdatePolicies = createUpdateModes();
}
protected IVMUpdatePolicy[] createUpdateModes() {
return new IVMUpdatePolicy[] { new AutomaticUpdatePolicy() };
}
@Override
public IVMUpdatePolicy[] getAvailableUpdatePolicies() {
return fAvailableUpdatePolicies;
}
@Override
public IVMUpdatePolicy getActiveUpdatePolicy() {
String updateModeId = (String)getPresentationContext().getProperty(SELECTED_UPDATE_MODE);
if (updateModeId != null) {
for (IVMUpdatePolicy updateMode : getAvailableUpdatePolicies()) {
if (updateMode.getID().equals(updateModeId)) {
return updateMode;
}
}
}
// Default to the first one.
return getAvailableUpdatePolicies()[0];
}
@Override
public void setActiveUpdatePolicy(IVMUpdatePolicy updatePolicy) {
getPresentationContext().setProperty(SELECTED_UPDATE_MODE, updatePolicy.getID());
// Repaint the view to allow elements using the PROP_UPDATE_POLICY_ID
// property to repaint themselves.
for (final IVMModelProxy proxyStrategy : getActiveModelProxies()) {
if (!proxyStrategy.isDisposed()) {
proxyStrategy.fireModelChanged(new ModelDelta(proxyStrategy.getRootElement(), IModelDelta.CONTENT));
}
}
}
@Override
public void refresh() {
IElementUpdateTester elementTester = getActiveUpdatePolicy().getElementUpdateTester(ManualUpdatePolicy.REFRESH_EVENT);
for (final IVMModelProxy proxyStrategy : getActiveModelProxies()) {
flush(new FlushMarkerKey(proxyStrategy.getRootElement(), elementTester));
}
for (final IVMModelProxy proxyStrategy : getActiveModelProxies()) {
if (!proxyStrategy.isDisposed()) {
proxyStrategy.fireModelChanged(new ModelDelta(proxyStrategy.getRootElement(), IModelDelta.CONTENT));
}
}
}
@Override
public ICacheEntry getCacheEntry(IVMNode node, Object viewerInput, TreePath path) {
ElementDataKey key = makeEntryKey(node, viewerInput, path);
return getElementDataEntry(key, false);
}
@Override
public void updateNode(final IVMNode node, IHasChildrenUpdate[] updates) {
LinkedList <IHasChildrenUpdate> missUpdates = new LinkedList<IHasChildrenUpdate>();
for(final IHasChildrenUpdate update : updates) {
// Find or create the cache entry for the element of this update.
ElementDataKey key = makeEntryKey(node, update);
final ElementDataEntry entry = getElementDataEntry(key, true);
updateRootElementMarker(key.fRootElement, node, update);
// Check if the cache entry has this request result cached.
if (entry.fHasChildren != null) {
// Cache Hit! Just return the value.
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheHitHasChildren(node = " + node + ", update = " + update + ", " + entry.fHasChildren + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
update.setHasChilren(entry.fHasChildren.booleanValue());
update.done();
} else {
// Cache miss! Save the flush counter of the entry and create a proxy update.
final int flushCounter = entry.fFlushCounter;
missUpdates.add(
new VMHasChildrenUpdate(
update,
new ViewerDataRequestMonitor<Boolean>(getExecutor(), update) {
@Override
protected void handleCompleted() {
// Update completed. Write value to cache only if update succeeded
// and the cache entry wasn't flushed in the mean time.
if(isSuccess()) {
if (flushCounter == entry.fFlushCounter) {
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheSavedHasChildren(node = " + node + ", update = " + update + ", " + getData() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
entry.fHasChildren = this.getData();
}
update.setHasChilren(getData());
} else {
update.setStatus(getStatus());
}
update.done();
}
}));
}
}
// Issue all the update proxies with one call.
if (!missUpdates.isEmpty()) {
super.updateNode(node, missUpdates.toArray(new IHasChildrenUpdate[missUpdates.size()]));
}
}
@Override
public void updateNode(final IVMNode node, final IChildrenCountUpdate update) {
// Find or create the cache entry for the element of this update.
ElementDataKey key = makeEntryKey(node, update);
final ElementDataEntry entry = getElementDataEntry(key, true);
updateRootElementMarker(key.fRootElement, node, update);
// Check if the cache entry has this request result cached.
if(entry.fChildrenCount != null) {
// Cache Hit! Just return the value.
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheHitChildrenCount(node = " + node + ", update = " + update + ", " + entry.fChildrenCount + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
update.setChildCount(entry.fChildrenCount.intValue());
update.done();
} else {
// Cache miss! Save the flush counter of the entry and create a proxy update.
final int flushCounter = entry.fFlushCounter;
IChildrenCountUpdate updateProxy = new VMChildrenCountUpdate(
update,
new ViewerDataRequestMonitor<Integer>(getExecutor(), update) {
@Override
protected void handleCompleted() {
// Update completed. Write value to cache only if update succeeded
// and the cache entry wasn't flushed in the mean time.
if(isSuccess()) {
if (flushCounter == entry.fFlushCounter) {
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheSavedChildrenCount(node = " + node + ", update = " + update + ", " + getData() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
entry.fChildrenCount = this.getData();
}
update.setChildCount(getData());
} else {
update.setStatus(getStatus());
}
update.done();
}
});
super.updateNode(node, updateProxy);
}
}
@Override
public void updateNode(final IVMNode node, final IChildrenUpdate update) {
// Find or create the cache entry for the element of this update.
ElementDataKey key = makeEntryKey(node, update);
final ElementDataEntry entry = getElementDataEntry(key, true);
updateRootElementMarker(key.fRootElement, node, update);
final int flushCounter = entry.fFlushCounter;
if (entry.fChildren == null || (update.getOffset() < 0 && !entry.fAllChildrenKnown)) {
// Need to retrieve all the children if there is no children information yet.
// Or if the client requested all children (offset = -1, length -1) and all
// the children are not yet known.
IChildrenUpdate updateProxy = new VMChildrenUpdate(
update, update.getOffset(), update.getLength(),
new ViewerDataRequestMonitor<List<Object>>(getExecutor(), update){
@Override
protected void handleSuccess() {
// Check if the update retrieved all children by specifying "offset = -1, length = -1"
int updateOffset = update.getOffset();
if (updateOffset < 0)
{
updateOffset = 0;
if (entry.fFlushCounter == flushCounter) {
entry.fAllChildrenKnown = true;
}
}
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheSavedChildren(node = " + node + ", update = " + update + ", children = {" + updateOffset + "->" + (updateOffset + getData().size()) + "})"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
if (flushCounter == entry.fFlushCounter) {
entry.ensureChildrenMap();
}
// Set the children to map and update.
for(int j = 0; j < getData().size(); j++) {
int offset = updateOffset + j;
Object child = getData().get(j);
if (child != null) {
if (flushCounter == entry.fFlushCounter) {
entry.fChildren.put(offset, child);
}
update.setChild(child, offset);
}
}
update.done();
}
@Override
protected void handleCancel() {
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheCanceledChildren(node = " + node + ", update = " + update + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
super.handleCancel();
}
});
super.updateNode(node, updateProxy);
} else if (update.getOffset() < 0 ) {
// The update requested all children. Fill in all children assuming that
// the children array is complete.
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheHitChildren(node = " + node + ", update = " + update + ", children = " + entry.fChildren.keySet() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
// The following assert should never fail given the first if statement.
assert entry.fAllChildrenKnown;
// we have all of the children in cache; return from cache
for(int position = 0; position < entry.fChildren.size(); position++) {
update.setChild(entry.fChildren.get(position), position);
}
update.done();
} else {
// Update for a partial list of children was requested.
// Iterate through the known children and make a list of missing
// indexes.
List<Integer> childrenMissingFromCache = new LinkedList<Integer>();
for (int i = update.getOffset(); i < update.getOffset() + update.getLength(); i++) {
childrenMissingFromCache.add(i);
}
// Write known children from cache into the update.
for(Integer position = update.getOffset(); position < update.getOffset() + update.getLength(); position++) {
Object child = entry.fChildren.get(position);
if (child != null) {
update.setChild(entry.fChildren.get(position), position);
childrenMissingFromCache.remove(position);
}
}
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cachePartialHitChildren(node = " + node + ", update = " + update + ", missing = " + childrenMissingFromCache + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
if (!childrenMissingFromCache.isEmpty()) {
// Note: it is possible that entry.fAllChildrenKnown == true at this point.
// This can happen if the node's has children implementation returns true
// while the actual children update returns with no elements. A node
// may do this for optimization reasons. I.e. sometimes it may be more
// efficient to ask the user to expand a node to see if it has any
// children.
// Some children were not found in the cache, create separate
// proxy updates for the continuous ranges of missing children.
List<IChildrenUpdate> partialUpdates = new ArrayList<IChildrenUpdate>(2);
final CountingRequestMonitor multiRm = new ViewerCountingRequestMonitor(getExecutor(), update);
while(!childrenMissingFromCache.isEmpty())
{
final int offset = childrenMissingFromCache.get(0);
childrenMissingFromCache.remove(0);
int length = 1;
while(!childrenMissingFromCache.isEmpty() && childrenMissingFromCache.get(0) == offset + length)
{
length++;
childrenMissingFromCache.remove(0);
}
partialUpdates.add(new VMChildrenUpdate(
update, offset, length,
new DataRequestMonitor<List<Object>>(getExecutor(), multiRm) {
@Override
protected void handleSuccess() {
// Only save the children to the cahce if the entry wasn't flushed.
if (flushCounter == entry.fFlushCounter) {
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cachePartialSaveChildren(node = " + node + ", update = " + update + ", saved = {" + offset + "->" + (offset + getData().size()) + "})"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
entry.ensureChildrenMap();
}
for (int i = 0; i < getData().size(); i++) {
if (getData().get(i) != null) {
update.setChild(getData().get(i), offset + i);
if (flushCounter == entry.fFlushCounter) {
// Only save the children to the cahce if the entry wasn't flushed.
entry.fChildren.put(offset + i, getData().get(i));
}
}
}
multiRm.done();
}
}));
}
for (IChildrenUpdate partialUpdate : partialUpdates) {
super.updateNode(node, partialUpdate);
}
multiRm.setDoneCount(partialUpdates.size());
} else {
// All children were found in cache. Complete the update.
update.done();
}
}
}
/**
* Flushes the cache with given DMC as the root element.
* @param dmcToFlush DM Context which is the root of the flush operation. Entries
* for all DMCs that have this DMC as their ancestor will be flushed. If this
* parameter is null, then all entries are flushed.
* @param archive
*/
private void flush(FlushMarkerKey flushKey) {
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheFlushing(" + flushKey + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
// For each entry that has the given context as a parent, perform the flush.
// Iterate through the cache entries backwards. This means that we will be
// iterating in order of most-recently-used to least-recently-used.
Entry entry = fCacheListHead.fPrevious;
while (entry != fCacheListHead) {
if (entry.fKey instanceof FlushMarkerKey) {
FlushMarkerKey entryFlushKey = (FlushMarkerKey)entry.fKey;
// If the context currently being flushed includes the flush
// context in current entry, remove the current entry since it will
// be replaced with one at the end of the list.
// Use special handling for null contexts, which we treat like it's an
// ancestor of all other contexts.
if (flushKey.includes(entryFlushKey)) {
fCacheData.remove(entryFlushKey);
entry.remove();
}
// If the flush context in current entry includes the current context
// being flushed, we can stop iterating through the cache entries
// now.
if (entryFlushKey.includes(flushKey)) {
break;
}
}
else if (entry instanceof ElementDataEntry) {
ElementDataEntry elementDataEntry = (ElementDataEntry)entry;
ElementDataKey elementDataKey = (ElementDataKey)elementDataEntry.fKey;
int updateFlags = flushKey.getUpdateFlags(elementDataKey);
if ((updateFlags & IVMUpdatePolicy.FLUSH) != 0) {
if ((updateFlags & IVMUpdatePolicy.ARCHIVE) == IVMUpdatePolicy.ARCHIVE) {
// We are saving current data for change history, check if the data is valid.
// If it valid, save it for archive, if it's not valid old archive data will be used
// if there is any. And if there is no old archive data, just remove the cache entry.
if (elementDataEntry.fProperties != null) {
elementDataEntry.fArchiveProperties = elementDataEntry.fProperties;
}
elementDataEntry.fProperties = null;
// There is no archived data, which means that this entry is empty, so remove it from cache
// completely.
if (elementDataEntry.fArchiveProperties == null) {
fCacheData.remove(entry.fKey);
entry.remove();
}
} else {
// We are not changing the archived data. If archive data exists in the entry, leave it.
// Otherwise remove the whole entry.
if (elementDataEntry.fArchiveProperties != null) {
elementDataEntry.fProperties = null;
} else {
fCacheData.remove(entry.fKey);
entry.remove();
}
}
elementDataEntry.fFlushCounter++;
elementDataEntry.fHasChildren = null;
elementDataEntry.fChildrenCount = null;
elementDataEntry.fChildren = null;
elementDataEntry.fAllChildrenKnown = false;
elementDataEntry.fDirty = false;
} else if ((updateFlags & IVMUpdatePolicy.FLUSH_ALL_PROPERTIES) != 0) {
elementDataEntry.fProperties = null;
} else if ((updateFlags & IVMUpdatePolicy.FLUSH_PARTIAL_PROPERTIES) != 0) {
Collection<String> propertiesToFlush = flushKey.getPropertiesToFlush(elementDataKey, elementDataEntry.fDirty);
if (propertiesToFlush != null && elementDataEntry.fProperties != null) {
elementDataEntry.fProperties.keySet().removeAll(propertiesToFlush);
}
} else if ((updateFlags & IVMUpdatePolicy.DIRTY) != 0) {
elementDataEntry.fDirty = true;
if (elementDataEntry.fProperties != null) {
elementDataEntry.fProperties.put(PROP_CACHE_ENTRY_DIRTY, Boolean.TRUE);
}
}
}
entry = entry.fPrevious;
}
// Insert a marker for this flush operation.
Entry flushMarkerEntry = new Entry(flushKey);
fCacheData.put(flushKey, flushMarkerEntry);
flushMarkerEntry.insert(fCacheListHead);
}
/**
* Listener used to detect when the viewer is finished updating itself
* after a model event. The
*/
// Warnings for use of ITreeModelViewer. ITreeModelViewer is an internal
// interface in platform, but it is more generic than the public TreeModelViewer.
// Using ITreeModelViewer will allow us to write unit tests using the
// VirtualTreeModelViewer.
@SuppressWarnings("restriction")
private class ViewUpdateFinishedListener implements IViewerUpdateListener, IModelChangedListener {
private final org.eclipse.debug.internal.ui.viewers.model.ITreeModelViewer fViewer;
private boolean fViewerChangeStarted = false;
private RequestMonitor fRm;
ViewUpdateFinishedListener(org.eclipse.debug.internal.ui.viewers.model.ITreeModelViewer viewer) {
fViewer = viewer;
}
private void start(RequestMonitor rm) {
synchronized(this) {
fViewer.addModelChangedListener(this);
fViewer.addViewerUpdateListener(this);
fRm = rm;
}
}
@Override
public synchronized void viewerUpdatesComplete() {
done();
}
@Override
public void modelChanged(IModelDelta delta, IModelProxy proxy) {
synchronized (this) {
if (!fViewerChangeStarted) {
done();
}
}
}
@Override
public void viewerUpdatesBegin() {
synchronized(this) {
fViewerChangeStarted = true;
}
}
private synchronized void done() {
if (fRm != null) {
fRm.done();
fViewer.removeViewerUpdateListener(this);
fViewer.removeModelChangedListener(this);
}
}
@Override
public void updateStarted(IViewerUpdate update) {}
@Override
public void updateComplete(IViewerUpdate update) {}
}
@Override
protected void handleEvent(final IVMModelProxy proxyStrategy, final Object event, final RequestMonitor rm) {
IElementUpdateTester elementTester = getActiveUpdatePolicy().getElementUpdateTester(event);
flush(new FlushMarkerKey(proxyStrategy.getRootElement(), elementTester));
if (!proxyStrategy.isDisposed()) {
if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
trace(event, null, proxyStrategy, EventHandlerAction.processing);
}
proxyStrategy.createDelta(
event,
new DataRequestMonitor<IModelDelta>(getExecutor(), rm) {
@Override
public void handleSuccess() {
if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
trace(event, null, proxyStrategy, EventHandlerAction.firedDeltaFor);
}
// If we need to wait for the view to finish updating, then before posting the delta to the
// viewer install a listener, which will in turn call rm.done().
if (fDelayEventHandleForViewUpdate) {
@SuppressWarnings("restriction")
org.eclipse.debug.internal.ui.viewers.model.ITreeModelViewer viewer =
(org.eclipse.debug.internal.ui.viewers.model.ITreeModelViewer) proxyStrategy.getViewer();
new ViewUpdateFinishedListener(viewer).start(rm);
}
proxyStrategy.fireModelChanged(getData());
if (!fDelayEventHandleForViewUpdate) {
rm.done();
}
}
@Override public String toString() {
return "Result of a delta for event: '" + event.toString() + "' in VMP: '" + AbstractCachingVMProvider.this + "'" + "\n" + getData().toString(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
});
} else {
rm.done();
}
}
/**
* Override default implementation to avoid automatically removing disposed proxies from
* list of active proxies. The caching provider only removes a proxy after its root element
* is no longer in the cache.
*/
@Override
public IModelProxy createModelProxy(Object element, IPresentationContext context) {
// Iterate through the current active proxies to try to find a proxy with the same
// element and re-use it if found. Only disposed proxies can be re-used because
// multiple viewers cannot use the same proxy.
//
// Unlike in the base class, do not remove proxies just because they were disposed
// by the viewer. These proxies can contain modification history for variables in
// their cache. The proxies will be removed once their cache entries are emptied.
// See rootElementRemovedFromCache().
IVMModelProxy proxy = null;
for (Iterator<IVMModelProxy> itr = getActiveModelProxies().iterator(); itr.hasNext();) {
IVMModelProxy next = itr.next();
if (next != null && next.getRootElement().equals(element) && next.isDisposed()) {
proxy = next;
break;
}
}
if (proxy == null) {
proxy = createModelProxyStrategy(element);
getActiveModelProxies().add(proxy);
} else if (proxy.isDisposed()) {
// DSF is capable of re-using old proxies which were previously
// disposed. However, the viewer which installs a proxy using
// a background job to install the proxy calls
// IModelProxy.isDisposed(), to check whether the proxy was disposed
// before it could be installed. We need to clear the disposed flag
// of the re-used proxy here, otherwise the proxy will never get used.
// Calling init here will cause the init() method to be called twice
// so the IVMModelProxy needs to be prepared for that.
// See bug 241024.
proxy.init(context);
}
return proxy;
}
/**
* Called when all cache entries for the given root element have been
* removed from the cache. In order to properly track changed elements, the
* caching VM provider does not immediately forget about the proxy for a
* given root element when the viewer root element changes. Instead it holds
* on to the proxy and keeps processing deltas for that root element until
* the cache entries for this element are gone.
*/
protected void rootElementRemovedFromCache(Object rootElement) {
fRootMarkers.remove(rootElement);
for (Iterator<IVMModelProxy> proxiesItr = getActiveModelProxies().iterator(); proxiesItr.hasNext();) {
IVMModelProxy proxy = proxiesItr.next();
if (proxy.isDisposed() && proxy.getRootElement().equals(rootElement) ) {
proxiesItr.remove();
}
}
}
/**
* Convenience class that searches for the root element for the given
* update and creates an element cache entry key.
*/
private ElementDataKey makeEntryKey(IVMNode node, IViewerUpdate update) {
return makeEntryKey(node, update.getViewerInput(), update.getElementPath());
}
/**
* Convenience class that searches for the root element for the given
* update and creates an element cache entry key.
*/
private ElementDataKey makeEntryKey(IVMNode node, Object viewerInput, TreePath path) {
Object rootElement = viewerInput; // Default
outer: for (IVMModelProxy proxy : getActiveModelProxies()) {
Object proxyRoot = proxy.getRootElement();
if (proxyRoot.equals(viewerInput)) {
rootElement = proxyRoot;
break;
}
for (int i = 0; i < path.getSegmentCount(); i++) {
if (proxyRoot.equals(path.getSegment(i))) {
rootElement = proxyRoot;
break outer;
}
}
}
return new ElementDataKey(rootElement, node, viewerInput, path);
}
/**
* This is the only method that should be used to access a cache entry.
* It creates a new entry if needed and it maintains the ordering in
* the least-recently-used linked list.
* @param create Create the entry if needed.
* @return cache element entry, may be <code>null</code> if entry does
* not exist and the create parameter is <code>false</code>
*/
private ElementDataEntry getElementDataEntry(ElementDataKey key, boolean create) {
assert key != null;
ElementDataEntry entry = (ElementDataEntry)fCacheData.get(key);
if (entry != null) {
// Entry exists, move it to the end of the list.
entry.reinsert(fCacheListHead);
} else if (create) {
// Create a new entry and add it to the end of the list.
entry = new ElementDataEntry(key);
addEntry(key, entry);
}
return entry;
}
private void updateRootElementMarker(Object rootElement, IVMNode node, IViewerUpdate update) {
boolean created = false;
// Update the root element marker:
// - ensure that the root marker is root markers' map,
// - ensure that the root marker is in the cache map,
// - and ensure that it's at the end of the cache.
RootElementMarkerKey rootMarker = fRootMarkers.get(rootElement);
if (rootMarker == null) {
rootMarker = new RootElementMarkerKey(rootElement);
fRootMarkers.put(rootElement, rootMarker);
created = true;
}
Entry rootMarkerEntry = fCacheData.get(rootMarker);
if (rootMarkerEntry == null) {
rootMarkerEntry = new RootElementMarkerEntry(rootMarker);
addEntry(rootMarker, rootMarkerEntry);
} else if (rootMarkerEntry.fNext != fCacheListHead) {
rootMarkerEntry.reinsert(fCacheListHead);
}
if (created) {
ElementDataKey rootElementDataKey =
new ElementDataKey(rootElement, node, update.getViewerInput(), update.getElementPath());
ElementDataEntry entry = getElementDataEntry(rootElementDataKey, false);
Object[] rootElementChildren = getActiveUpdatePolicy().getInitialRootElementChildren(rootElement);
if (rootElementChildren != null) {
entry.fHasChildren = rootElementChildren.length > 0;
entry.fChildrenCount = rootElementChildren.length;
entry.fChildren = new HashMap<Integer, Object>(entry.fChildrenCount * 4/3);
for (int i = 0; i < rootElementChildren.length; i++) {
entry.fChildren.put(i, rootElementChildren[i]);
}
entry.fAllChildrenKnown = true;
entry.fDirty = true;
}
Map<String, Object> rootElementProperties = getActiveUpdatePolicy().getInitialRootElementProperties(rootElement);
if (rootElementProperties != null) {
entry.fProperties = new HashMap<String, Object>((rootElementProperties.size() + 1) * 4/3);
entry.fProperties.putAll(rootElementProperties);
entry.fProperties.put(PROP_CACHE_ENTRY_DIRTY, true);
entry.fDirty = true;
}
}
}
/**
* Convenience method used by {@link #getElementDataEntry(ElementDataKey)}
*/
private void addEntry(Object key, Entry entry) {
fCacheData.put(key, entry);
entry.insert(fCacheListHead);
// If we are at capacity in the cache, remove the entry from head.
if (fCacheData.size() > MAX_CACHE_SIZE) {
fCacheData.remove(fCacheListHead.fNext.fKey);
fCacheListHead.fNext.remove();
}
}
/**
* @since 2.0
*/
@Override
public void update(IPropertiesUpdate[] updates) {
if (updates.length == 0)
return;
// Optimization: if all the updates belong to the same node, avoid
// creating any new lists/arrays.
boolean allNodesTheSame = true;
IVMNode firstNode = getNodeForElement(updates[0].getElement());
for (int i = 1; i < updates.length; i++) {
if (firstNode != getNodeForElement(updates[i].getElement())) {
allNodesTheSame = false;
break;
}
}
if (allNodesTheSame) {
if ( !(firstNode instanceof IElementPropertiesProvider) ) {
for (IPropertiesUpdate update : updates) {
update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE, "Element is not a VM Context or its node is not a properties provider.", null)); //$NON-NLS-1$
update.done();
}
} else {
updateNode(firstNode, updates);
}
} else {
// Sort the updates by the node.
Map<IVMNode, List<IPropertiesUpdate>> nodeUpdatesMap = new HashMap<IVMNode, List<IPropertiesUpdate>>();
for (IPropertiesUpdate update : updates) {
// Get the VM Context for last element in path.
IVMNode node = getNodeForElement(update.getElement());
if ( node == null || !(node instanceof IElementPropertiesProvider) ) {
// Misdirected update.
update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE, "Element is not a VM Context or its node is not a properties provider.", null)); //$NON-NLS-1$
update.done();
continue;
}
if (!nodeUpdatesMap.containsKey(node)) {
nodeUpdatesMap.put(node, new ArrayList<IPropertiesUpdate>());
}
nodeUpdatesMap.get(node).add(update);
}
// Iterate through the nodes in the sorted map.
for (IVMNode node : nodeUpdatesMap.keySet()) {
updateNode(node, nodeUpdatesMap.get(node).toArray(
new IPropertiesUpdate[nodeUpdatesMap.get(node).size()]));
}
}
}
/**
* Convenience method that finds the VM node corresponding to given element.
* It returns <code>null</code> if the element is not a VM context.
*
* @param element Element to find the VM Node for.
* @return View Model Node that this element was created by, or <code>null</code>.
*
* @since 2.0
*/
private IVMNode getNodeForElement(Object element) {
if (element instanceof IVMContext) {
return ((IVMContext) element).getVMNode();
}
return null;
}
protected void updateNode(final IVMNode node, IPropertiesUpdate[] updates) {
LinkedList <IPropertiesUpdate> missUpdates = new LinkedList<IPropertiesUpdate>();
for(final IPropertiesUpdate update : updates) {
// Find or create the cache entry for the element of this update.
ElementDataKey key = makeEntryKey(node, update);
final ElementDataEntry entry = getElementDataEntry(key, true);
updateRootElementMarker(key.fRootElement, node, update);
// The request can be retrieved from cache if all the properties that were requested in the update are
// found in the map.
if (entry.fProperties != null && entry.fProperties.keySet().containsAll(update.getProperties())) {
// Cache Hit! Just return the value.
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheHitProperties(node = " + node + ", update = " + update + ", " + entry.fProperties + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
if (entry.fProperties.containsKey(PROP_UPDATE_POLICY_ID)) {
entry.fProperties.put(PROP_UPDATE_POLICY_ID, getActiveUpdatePolicy().getID());
}
update.setAllProperties(entry.fProperties);
update.setStatus((IStatus)entry.fProperties.get(PROP_UPDATE_STATUS));
update.done();
} else {
// Cache miss! Check if already cached properties can be re-used.
Set<String> missingProperties = null;
if (entry.fProperties != null) {
missingProperties = new HashSet<String>(update.getProperties().size() * 4/3);
missingProperties.addAll(update.getProperties());
missingProperties.removeAll(entry.fProperties.keySet());
if (entry.fDirty) {
// Cache miss, BUT the entry is dirty already. Determine which properties can still be updated
// (if any), then request the missing properties from node, or return an error.
if (getActiveUpdatePolicy() instanceof IVMUpdatePolicyExtension) {
IVMUpdatePolicyExtension updatePolicyExt = (IVMUpdatePolicyExtension)getActiveUpdatePolicy();
for (Iterator<String> itr = missingProperties.iterator(); itr.hasNext();) {
String missingProperty = itr.next();
if ( !updatePolicyExt.canUpdateDirtyProperty(entry, missingProperty) ) {
itr.remove();
PropertiesUpdateStatus.getPropertiesStatus(update).setStatus(
missingProperty,
DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_STATE, "Cache contains stale data. Refresh view.", null ));//$NON-NLS-1$
}
}
} else {
PropertiesUpdateStatus.getPropertiesStatus(update).setStatus(
missingProperties.toArray(new String[missingProperties.size()]),
DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_STATE, "Cache contains stale data. Refresh view.", null ));//$NON-NLS-1$
missingProperties.clear();
}
if (missingProperties.isEmpty()) {
if (entry.fProperties.containsKey(PROP_UPDATE_POLICY_ID)) {
entry.fProperties.put(PROP_UPDATE_POLICY_ID, getActiveUpdatePolicy().getID());
}
update.setAllProperties(entry.fProperties);
update.done();
return;
}
}
} else {
missingProperties = update.getProperties();
}
final Set<String> _missingProperties = missingProperties;
// Save the flush counter of the entry and create a proxy update.
final int flushCounter = entry.fFlushCounter;
missUpdates.add(new VMPropertiesUpdate(
missingProperties,
update,
new ViewerDataRequestMonitor<Map<String, Object>>(getExecutor(), update) {
@Override
protected void handleCompleted() {
PropertiesUpdateStatus missUpdateStatus = PropertiesUpdateStatus.makePropertiesStatus(getStatus());
Map<String, Object> cachedProperties;
PropertiesUpdateStatus cachedStatus;
if (!isCanceled() && flushCounter == entry.fFlushCounter) {
// We are caching the result of this update. Copy the properties from the update
// to the cached properties map.
if (entry.fProperties == null) {
entry.fProperties = new HashMap<String, Object>((getData().size() + 3) * 4/3);
if (update.getProperties().contains(PROP_CACHE_ENTRY_DIRTY)) {
entry.fProperties.put(PROP_CACHE_ENTRY_DIRTY, entry.fDirty);
}
entry.fProperties.put(PROP_UPDATE_STATUS, new PropertiesUpdateStatus());
}
cachedProperties = entry.fProperties;
cachedProperties.putAll(getData());
// Make sure that all the properties that were requested by the update object are in
// the cache entry's properties map. It's possible he ViewerDataRequestMonitor was able
// to provide us only a subset of the requested ones. We want to prevent that from
// causing future cache misses, since a cache hit requires the cache entry to contain
// all requested properties. Use a null value for the missing items.
for (String property : _missingProperties) {
if (!getData().containsKey(property)) {
cachedProperties.put(property, null);
}
}
// Merge status from properties that came back from the node into the status that's in
// the cache.
cachedStatus = (PropertiesUpdateStatus)cachedProperties.get(PROP_UPDATE_STATUS);
cachedStatus = PropertiesUpdateStatus.mergePropertiesStatus(
cachedStatus, missUpdateStatus, _missingProperties);
cachedProperties.put(PROP_UPDATE_STATUS, cachedStatus);
} else {
// We are not caching the result of this update, but we should still return valid data
// to the client. In case the update was canceled we can also return valid data to the
// client even if the client is likely to ignore it since the cost of doing so is
// relatively low.
// Create a temporary cached properties map and add existing cache and node update
// properties to it.
if (entry.fProperties != null) {
cachedProperties = new HashMap<String, Object>((entry.fProperties.size() + getData().size() + 3) * 4/3);
cachedProperties.putAll(entry.fProperties);
cachedStatus = PropertiesUpdateStatus.mergePropertiesStatus(
(PropertiesUpdateStatus)cachedProperties.get(PROP_UPDATE_STATUS),
missUpdateStatus, _missingProperties);
} else {
cachedProperties = new HashMap<String, Object>((getData().size() + 3) * 4/3);
cachedStatus = missUpdateStatus;
}
cachedProperties.putAll(getData());
cachedProperties.put(PROP_UPDATE_STATUS, missUpdateStatus);
if (update.getProperties().contains(PROP_CACHE_ENTRY_DIRTY)) {
cachedProperties.put(PROP_CACHE_ENTRY_DIRTY, Boolean.TRUE);
}
}
// Refresh the update policy property.
if (update.getProperties().contains(PROP_UPDATE_POLICY_ID)) {
cachedProperties.put(PROP_UPDATE_POLICY_ID, getActiveUpdatePolicy().getID());
}
// If there is archive data available, calculate the requested changed value properties.
// Do not calculate the changed flags if the entry has been flushed.
if (entry.fArchiveProperties != null && flushCounter == entry.fFlushCounter) {
for (String updateProperty : update.getProperties()) {
if (updateProperty.startsWith(PROP_IS_CHANGED_PREFIX)) {
String changedPropertyName = updateProperty.substring(LENGTH_PROP_IS_CHANGED_PREFIX);
Object newValue = cachedProperties.get(changedPropertyName);
Object oldValue = entry.fArchiveProperties.get(changedPropertyName);
if (oldValue != null) {
cachedProperties.put(updateProperty, !oldValue.equals(newValue));
}
}
}
}
if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) {
DsfUIPlugin.debug("cacheSavedProperties(node = " + node + ", update = " + update + ", " + getData() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
// Fill in requested properties and status into the update.
for (String property : update.getProperties()) {
update.setProperty(property, cachedProperties.get(property));
}
PropertiesUpdateStatus updateStatus = PropertiesUpdateStatus.makePropertiesStatus(update.getStatus());
updateStatus = PropertiesUpdateStatus.mergePropertiesStatus(
updateStatus, cachedStatus, update.getProperties());
update.setStatus(updateStatus);
update.done();
}
}));
}
}
// Issue all the update proxies with one call.
if (!missUpdates.isEmpty()) {
((IElementPropertiesProvider)node).update(missUpdates.toArray(new IPropertiesUpdate[missUpdates.size()]));
}
}
@Override
public boolean shouldWaitHandleEventToComplete() {
return fDelayEventHandleForViewUpdate;
}
/**
* @since 1.1
*/
protected void setDelayEventHandleForViewUpdate(boolean on) {
fDelayEventHandleForViewUpdate = on;
}
/**
* Used for tracing event handling
* <p>
* Note: this enum is duplicated from AbstractVMProvider.
*/
private enum EventHandlerAction {
received,
queued,
processing,
firedDeltaFor,
skipped,
canceled
}
/**
* Trace that we've reached a particular phase of the handling of an event
* for a particular proxy.
* <p>
* Note: this method is duplicated from AbstractVMProvider.
*
* @param event
* the event being handled
* @param skippedOrCanceledEvent
* for a 'skip' or 'cancel' action, this is the event that is
* being dismissed. Otherwise null
* @param proxy
* the target proxy; n/a (null) for a 'received' action.
* @param action
* what phased of the event handling has beeb reached
*/
private void trace(Object event, Object skippedOrCanceledEvent, IVMModelProxy proxy, EventHandlerAction action) {
assert DEBUG_DELTA;
StringBuilder str = new StringBuilder();
str.append(DsfPlugin.getDebugTime());
str.append(' ');
if (action == EventHandlerAction.skipped || action == EventHandlerAction.canceled) {
str.append(LoggingUtils.toString(this)).append(' ').append(action).append(" event ").append(LoggingUtils.toString(skippedOrCanceledEvent)).append(" because of event ").append(LoggingUtils.toString(event)); //$NON-NLS-1$ //$NON-NLS-2$
}
else {
str.append(LoggingUtils.toString(this)).append(' ').append(action).append(" event ").append(LoggingUtils.toString(event)); //$NON-NLS-1$
}
if (action != EventHandlerAction.received) {
str.append(" for proxy ").append(LoggingUtils.toString(proxy)).append( ", whose root is ").append(LoggingUtils.toString(proxy.getRootElement())); //$NON-NLS-1$ //$NON-NLS-2$
}
DsfUIPlugin.debug(str.toString());
}
}