/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.core.internal.events; import java.util.Iterator; import java.util.Map; 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; } /* * @see IResourceDelta#accept(IResourceDeltaVisitor) */ public void accept(IResourceDeltaVisitor visitor) throws CoreException { accept(visitor, 0); } /* * @see IResourceDelta#accept(IResourceDeltaVisitor, boolean) */ public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException { accept(visitor, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0); } /* * @see IResourceDelta#accept(IResourceDeltaVisitor, int) */ 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 (int i= 0; i < children.length; i++) { ResourceDelta childDelta= children[i]; // 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= (MarkerSet)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; } } } /** * @see IResourceDelta#findMember(IPath) */ 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 (int i= 0; i < children.length; i++) children[i].fixMovesAndMarkers(oldTree); } /** * @see IResourceDelta#getAffectedChildren() */ public IResourceDelta[] getAffectedChildren() { return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE); } /** * @see IResourceDelta#getAffectedChildren(int) */ public IResourceDelta[] getAffectedChildren(int kindMask) { return getAffectedChildren(kindMask, IResource.NONE); } /* * @see IResourceDelta#getAffectedChildren(int, int) */ 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; } /** * @see IResourceDelta#getFlags() */ public int getFlags() { return status & ~KIND_MASK; } /** * @see IResourceDelta#getFullPath() */ public IPath getFullPath() { return path; } /** * @see IResourceDelta#getKind() */ public int getKind() { return status & KIND_MASK; } /** * @see IResourceDelta#getMarkerDeltas() */ public IMarkerDelta[] getMarkerDeltas() { Map markerDeltas= deltaInfo.getMarkerDeltas(); if (markerDeltas == null) return EMPTY_MARKER_DELTAS; if (path == null) path= Path.ROOT; MarkerSet changes= (MarkerSet)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; } /** * @see IResourceDelta#getMovedFromPath() */ public IPath getMovedFromPath() { if ((status & MOVED_FROM) != 0) { return deltaInfo.getNodeIDMap().getOldPath(newInfo.getNodeId()); } return null; } /** * @see IResourceDelta#getMovedToPath() */ public IPath getMovedToPath() { if ((status & MOVED_TO) != 0) { return deltaInfo.getNodeIDMap().getNewPath(oldInfo.getNodeId()); } return null; } /** * @see IResourceDelta#getProjectRelativePath() */ 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); } /** * @see IResourceDelta#getResource() */ 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; if (info == null) Assert.isNotNull(null, "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 StringBuffer buffer= new StringBuffer(); writeDebugString(buffer); return buffer.toString(); } /** * Returns a string representation of this delta's deep structure suitable for debug purposes. */ public String toDeepDebugString() { final StringBuffer buffer= new StringBuffer("\n"); //$NON-NLS-1$ writeDebugString(buffer); for (int i= 0; i < children.length; ++i) buffer.append(children[i].toDeepDebugString()); return buffer.toString(); } /** * For debugging only */ 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 markers) { deltaInfo.setMarkerDeltas(markers); } /** * Writes a string representation of this delta's immediate structure on the given string * buffer. */ public void writeDebugString(StringBuffer 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(StringBuffer buffer) { Map markerDeltas= deltaInfo.getMarkerDeltas(); if (markerDeltas == null || markerDeltas.isEmpty()) return; buffer.append('['); for (Iterator e= markerDeltas.keySet().iterator(); e.hasNext();) { IPath key= (IPath)e.next(); if (getResource().getFullPath().equals(key)) { IMarkerSetElement[] deltas= ((MarkerSet)markerDeltas.get(key)).elements(); boolean addComma= false; for (int i= 0; i < deltas.length; i++) { IMarkerDelta delta= (IMarkerDelta)deltas[i]; 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(']'); } }