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
/******************************************************************************* * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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
/** * 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.
/** * 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.
/** * 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(']'); } }