blob: 35d488345763accd3c3ba783b640f3502ce82a39 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* James Blackburn (Broadcom Corp.) - ongoing development
* Mickael Istria (Red Hat Inc.) - Bug 488938
*******************************************************************************/
package org.eclipse.core.internal.events;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.internal.resources.*;
import org.eclipse.core.internal.watson.ElementTree;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
/**
* Concrete implementation of the IResourceDelta interface. Each ResourceDelta
* object represents changes that have occurred between two states of the
* resource tree.
*/
public class ResourceDelta extends PlatformObject implements IResourceDelta {
protected IPath path;
protected ResourceDeltaInfo deltaInfo;
protected int status;
protected ResourceInfo oldInfo;
protected ResourceInfo newInfo;
protected ResourceDelta[] children;
// don't aggressively set this, but cache it if called once
protected IResource cachedResource;
//
protected static int KIND_MASK = 0xFF;
private static IMarkerDelta[] EMPTY_MARKER_DELTAS = new IMarkerDelta[0];
protected ResourceDelta(IPath path, ResourceDeltaInfo deltaInfo) {
this.path = path;
this.deltaInfo = deltaInfo;
}
@Override
public void accept(IResourceDeltaVisitor visitor) throws CoreException {
accept(visitor, 0);
}
@Override
public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException {
accept(visitor, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0);
}
@Override
public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException {
final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0;
final boolean includeTeamPrivate = (memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) != 0;
final boolean includeHidden = (memberFlags & IContainer.INCLUDE_HIDDEN) != 0;
int mask = includePhantoms ? ALL_WITH_PHANTOMS : REMOVED | ADDED | CHANGED;
if ((getKind() & mask) == 0)
return;
if (!visitor.visit(this))
return;
for (ResourceDelta childDelta : children) {
// quietly exclude team-private, hidden and phantom members unless explicitly included
if (!includeTeamPrivate && childDelta.isTeamPrivate())
continue;
if (!includePhantoms && childDelta.isPhantom())
continue;
if (!includeHidden && childDelta.isHidden())
continue;
childDelta.accept(visitor, memberFlags);
}
}
/**
* Check for marker deltas, and set the appropriate change flag if there are any.
*/
protected void checkForMarkerDeltas() {
if (deltaInfo.getMarkerDeltas() == null)
return;
int kind = getKind();
// Only need to check for added and removed, or for changes on the workspace.
// For changed, the bit is set in the comparator.
if (path.isRoot() || kind == ADDED || kind == REMOVED) {
MarkerSet changes = deltaInfo.getMarkerDeltas().get(path);
if (changes != null && changes.size() > 0) {
status |= MARKERS;
// If there have been marker changes, then ensure kind is CHANGED (if not ADDED or REMOVED).
// See 1FV9K20: ITPUI:WINNT - severe - task list - add or delete not working
if (kind == 0)
status |= CHANGED;
}
}
}
@Override
public IResourceDelta findMember(IPath path) {
int segmentCount = path.segmentCount();
if (segmentCount == 0)
return this;
//iterate over the path and find matching child delta
ResourceDelta current = this;
segments: for (int i = 0; i < segmentCount; i++) {
IResourceDelta[] currentChildren = current.children;
for (int j = 0, jmax = currentChildren.length; j < jmax; j++) {
if (currentChildren[j].getFullPath().lastSegment().equals(path.segment(i))) {
current = (ResourceDelta) currentChildren[j];
continue segments;
}
}
//matching child not found, return
return null;
}
return current;
}
/**
* Delta information on moves and on marker deltas can only be computed after
* the delta has been built. This method fixes up the delta to accurately
* reflect moves (setting MOVED_FROM and MOVED_TO), and marker changes on
* added and removed resources.
*/
protected void fixMovesAndMarkers(ElementTree oldTree) {
NodeIDMap nodeIDMap = deltaInfo.getNodeIDMap();
if (!path.isRoot() && !nodeIDMap.isEmpty()) {
int kind = getKind();
switch (kind) {
case CHANGED :
case ADDED :
IPath oldPath = nodeIDMap.getOldPath(newInfo.getNodeId());
if (oldPath != null && !oldPath.equals(path)) {
//get the old info from the old tree
ResourceInfo actualOldInfo = (ResourceInfo) oldTree.getElementData(oldPath);
// Replace change flags by comparing old info with new info,
// Note that we want to retain the kind flag, but replace all other flags
// This is done only for MOVED_FROM, not MOVED_TO, since a resource may be both.
status = (status & KIND_MASK) | (deltaInfo.getComparator().compare(actualOldInfo, newInfo) & ~KIND_MASK);
status |= MOVED_FROM;
//our API states that MOVED_FROM must be in conjunction with ADDED | (CHANGED + REPLACED)
if (kind == CHANGED)
status = status | REPLACED | CONTENT;
//check for gender change
if (oldInfo != null && newInfo != null && oldInfo.getType() != newInfo.getType())
status |= TYPE;
}
}
switch (kind) {
case REMOVED :
case CHANGED :
IPath newPath = nodeIDMap.getNewPath(oldInfo.getNodeId());
if (newPath != null && !newPath.equals(path)) {
status |= MOVED_TO;
//our API states that MOVED_TO must be in conjunction with REMOVED | (CHANGED + REPLACED)
if (kind == CHANGED)
status = status | REPLACED | CONTENT;
}
}
}
//check for marker deltas -- this is affected by move computation
//so must happen afterwards
checkForMarkerDeltas();
//recurse on children
for (ResourceDelta element : children)
element.fixMovesAndMarkers(oldTree);
}
@Override
public IResourceDelta[] getAffectedChildren() {
return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
}
@Override
public IResourceDelta[] getAffectedChildren(int kindMask) {
return getAffectedChildren(kindMask, IResource.NONE);
}
@Override
public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags) {
int numChildren = children.length;
//if there are no children, they all match
if (numChildren == 0)
return children;
boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0;
boolean includeTeamPrivate = (memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) != 0;
boolean includeHidden = (memberFlags & IContainer.INCLUDE_HIDDEN) != 0;
// reduce INCLUDE_PHANTOMS member flag to kind mask
if (includePhantoms)
kindMask |= ADDED_PHANTOM | REMOVED_PHANTOM;
//first count the number of matches so we can allocate the exact array size
int matching = 0;
for (int i = 0; i < numChildren; i++) {
if ((children[i].getKind() & kindMask) == 0)
continue;// child has wrong kind
if (!includePhantoms && children[i].isPhantom())
continue;
if (!includeTeamPrivate && children[i].isTeamPrivate())
continue; // child has is a team-private member which are not included
if (!includeHidden && children[i].isHidden())
continue;
matching++;
}
//use arraycopy if all match
if (matching == numChildren) {
IResourceDelta[] result = new IResourceDelta[children.length];
System.arraycopy(children, 0, result, 0, children.length);
return result;
}
//create the appropriate sized array and fill it
IResourceDelta[] result = new IResourceDelta[matching];
int nextPosition = 0;
for (int i = 0; i < numChildren; i++) {
if ((children[i].getKind() & kindMask) == 0)
continue; // child has wrong kind
if (!includePhantoms && children[i].isPhantom())
continue;
if (!includeTeamPrivate && children[i].isTeamPrivate())
continue; // child has is a team-private member which are not included
if (!includeHidden && children[i].isHidden())
continue;
result[nextPosition++] = children[i];
}
return result;
}
protected ResourceDeltaInfo getDeltaInfo() {
return deltaInfo;
}
@Override
public int getFlags() {
return status & ~KIND_MASK;
}
@Override
public IPath getFullPath() {
return path;
}
@Override
public int getKind() {
return status & KIND_MASK;
}
@Override
public IMarkerDelta[] getMarkerDeltas() {
Map<IPath, MarkerSet> markerDeltas = deltaInfo.getMarkerDeltas();
if (markerDeltas == null)
return EMPTY_MARKER_DELTAS;
if (path == null)
path = Path.ROOT;
MarkerSet changes = markerDeltas.get(path);
if (changes == null)
return EMPTY_MARKER_DELTAS;
IMarkerSetElement[] elements = changes.elements();
IMarkerDelta[] result = new IMarkerDelta[elements.length];
for (int i = 0; i < elements.length; i++)
result[i] = (IMarkerDelta) elements[i];
return result;
}
@Override
public IPath getMovedFromPath() {
if ((status & MOVED_FROM) != 0) {
return deltaInfo.getNodeIDMap().getOldPath(newInfo.getNodeId());
}
return null;
}
@Override
public IPath getMovedToPath() {
if ((status & MOVED_TO) != 0) {
return deltaInfo.getNodeIDMap().getNewPath(oldInfo.getNodeId());
}
return null;
}
@Override
public IPath getProjectRelativePath() {
IPath full = getFullPath();
int count = full.segmentCount();
if (count < 0)
return null;
if (count <= 1) // 0 or 1
return Path.EMPTY;
return full.removeFirstSegments(1);
}
@Override
public IResource getResource() {
// return a cached copy if we have one
if (cachedResource != null)
return cachedResource;
// if this is a delta for the root then return the root resource
if (path.segmentCount() == 0)
return deltaInfo.getWorkspace().getRoot();
// if the delta is a remove then we have to look for the old info to find the type
// of resource to create.
ResourceInfo info = null;
if ((getKind() & (REMOVED | REMOVED_PHANTOM)) != 0)
info = oldInfo;
else
info = newInfo;
Assert.isNotNull(info, "Do not have resource info for resource in delta: " + path); //$NON-NLS-1$
cachedResource = deltaInfo.getWorkspace().newResource(path, info.getType());
return cachedResource;
}
/**
* Returns true if this delta represents a phantom member, and false
* otherwise.
*/
protected boolean isPhantom() {
//use old info for removals, and new info for added or changed
if ((status & (REMOVED | REMOVED_PHANTOM)) != 0)
return ResourceInfo.isSet(oldInfo.getFlags(), ICoreConstants.M_PHANTOM);
return ResourceInfo.isSet(newInfo.getFlags(), ICoreConstants.M_PHANTOM);
}
/**
* Returns true if this delta represents a team private member, and false
* otherwise.
*/
protected boolean isTeamPrivate() {
//use old info for removals, and new info for added or changed
if ((status & (REMOVED | REMOVED_PHANTOM)) != 0)
return ResourceInfo.isSet(oldInfo.getFlags(), ICoreConstants.M_TEAM_PRIVATE_MEMBER);
return ResourceInfo.isSet(newInfo.getFlags(), ICoreConstants.M_TEAM_PRIVATE_MEMBER);
}
/**
* Returns true if this delta represents a hidden member, and false
* otherwise.
*/
protected boolean isHidden() {
//use old info for removals, and new info for added or changed
if ((status & (REMOVED | REMOVED_PHANTOM)) != 0)
return ResourceInfo.isSet(oldInfo.getFlags(), ICoreConstants.M_HIDDEN);
return ResourceInfo.isSet(newInfo.getFlags(), ICoreConstants.M_HIDDEN);
}
protected void setChildren(ResourceDelta[] children) {
this.children = children;
}
protected void setNewInfo(ResourceInfo newInfo) {
this.newInfo = newInfo;
}
protected void setOldInfo(ResourceInfo oldInfo) {
this.oldInfo = oldInfo;
}
protected void setStatus(int status) {
this.status = status;
}
/**
* Returns a string representation of this delta's
* immediate structure suitable for debug purposes.
*/
public String toDebugString() {
final StringBuilder buffer = new StringBuilder();
writeDebugString(buffer);
return buffer.toString();
}
/**
* Returns a string representation of this delta's
* deep structure suitable for debug purposes.
*/
public String toDeepDebugString() {
final StringBuilder buffer = new StringBuilder("\n"); //$NON-NLS-1$
writeDebugString(buffer);
for (ResourceDelta element : children)
buffer.append(element.toDeepDebugString());
return buffer.toString();
}
/**
* For debugging only
*/
@Override
public String toString() {
return "ResourceDelta(" + path + ')'; //$NON-NLS-1$
}
/**
* Provides a new set of markers for the delta. This is used
* when the delta is reused in cases where the only changes
* are marker changes.
*/
public void updateMarkers(Map<IPath, MarkerSet> markers) {
deltaInfo.setMarkerDeltas(markers);
}
/**
* Writes a string representation of this delta's
* immediate structure on the given string buffer.
*/
public void writeDebugString(StringBuilder buffer) {
buffer.append(getFullPath());
buffer.append('[');
switch (getKind()) {
case ADDED :
buffer.append('+');
break;
case ADDED_PHANTOM :
buffer.append('>');
break;
case REMOVED :
buffer.append('-');
break;
case REMOVED_PHANTOM :
buffer.append('<');
break;
case CHANGED :
buffer.append('*');
break;
case NO_CHANGE :
buffer.append('~');
break;
default :
buffer.append('?');
break;
}
buffer.append("]: {"); //$NON-NLS-1$
int changeFlags = getFlags();
boolean prev = false;
if ((changeFlags & CONTENT) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("CONTENT"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & LOCAL_CHANGED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("LOCAL_CHANGED"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & MOVED_FROM) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("MOVED_FROM(" + getMovedFromPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
prev = true;
}
if ((changeFlags & MOVED_TO) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("MOVED_TO(" + getMovedToPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
prev = true;
}
if ((changeFlags & OPEN) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("OPEN"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & TYPE) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("TYPE"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & SYNC) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("SYNC"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & MARKERS) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("MARKERS"); //$NON-NLS-1$
writeMarkerDebugString(buffer);
prev = true;
}
if ((changeFlags & REPLACED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("REPLACED"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & DESCRIPTION) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("DESCRIPTION"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & ENCODING) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("ENCODING"); //$NON-NLS-1$
prev = true;
}
if ((changeFlags & DERIVED_CHANGED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("DERIVED_CHANGED"); //$NON-NLS-1$
prev = true;
}
buffer.append("}"); //$NON-NLS-1$
if (isTeamPrivate())
buffer.append(" (team private)"); //$NON-NLS-1$
if (isHidden())
buffer.append(" (hidden)"); //$NON-NLS-1$
}
public void writeMarkerDebugString(StringBuilder buffer) {
Map<IPath, MarkerSet> markerDeltas = deltaInfo.getMarkerDeltas();
if (markerDeltas == null || markerDeltas.isEmpty())
return;
buffer.append('[');
for (Entry<IPath, MarkerSet> entry : markerDeltas.entrySet()) {
IPath key = entry.getKey();
if (getResource().getFullPath().equals(key)) {
MarkerSet set = entry.getValue();
IMarkerSetElement[] deltas = set.elements();
boolean addComma = false;
for (IMarkerSetElement delta2 : deltas) {
IMarkerDelta delta = (IMarkerDelta) delta2;
if (addComma)
buffer.append(',');
switch (delta.getKind()) {
case IResourceDelta.ADDED :
buffer.append('+');
break;
case IResourceDelta.REMOVED :
buffer.append('-');
break;
case IResourceDelta.CHANGED :
buffer.append('*');
break;
}
buffer.append(delta.getId());
addComma = true;
}
}
}
buffer.append(']');
}
}