/******************************************************************************* * 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.resources; import java.io.*; import java.util.*; import org.eclipse.core.internal.localstore.SafeChunkyInputStream; import org.eclipse.core.internal.localstore.SafeFileInputStream; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.internal.utils.Policy; import org.eclipse.core.internal.watson.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; /** * A marker manager stores and retrieves markers on resources in the workspace. */ public class MarkerManager implements IManager { //singletons private static final MarkerInfo[] NO_MARKER_INFO= new MarkerInfo[0]; private static final IMarker[] NO_MARKERS= new IMarker[0]; protected MarkerTypeDefinitionCache cache= new MarkerTypeDefinitionCache(); private long changeId= 0; protected Map currentDeltas= null; protected final MarkerDeltaManager deltaManager= new MarkerDeltaManager(); protected Workspace workspace; protected MarkerWriter writer= new MarkerWriter(this); /** * Creates a new marker manager */ public MarkerManager(Workspace workspace) { this.workspace= workspace; } /* (non-Javadoc) * Adds the given markers to the given resource. * * @see IResource#createMarker(String) */ public void add(IResource resource, MarkerInfo newMarker) throws CoreException { Resource target= (Resource)resource; ResourceInfo info= workspace.getResourceInfo(target.getFullPath(), false, false); target.checkExists(target.getFlags(info), false); info= workspace.getResourceInfo(resource.getFullPath(), false, true); //resource may have been deleted concurrently -- just bail out if this happens if (info == null) return; // set the M_MARKERS_SNAP_DIRTY flag to indicate that this // resource's markers have changed since the last snapshot if (isPersistent(newMarker)) info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); //Concurrency: copy the marker set on modify MarkerSet markers= info.getMarkers(true); if (markers == null) markers= new MarkerSet(1); basicAdd(resource, markers, newMarker); if (!markers.isEmpty()) info.setMarkers(markers); } /** * Adds the new markers to the given set of markers. If added, the markers are associated with * the specified resource.IMarkerDeltas for Added markers are generated. */ private void basicAdd(IResource resource, MarkerSet markers, MarkerInfo newMarker) throws CoreException { // should always be a new marker. if (newMarker.getId() != MarkerInfo.UNDEFINED_ID) { String message= Messages.resources_changeInAdd; throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, resource.getFullPath(), message)); } newMarker.setId(workspace.nextMarkerId()); markers.add(newMarker); IMarkerSetElement[] changes= new IMarkerSetElement[1]; changes[0]= new MarkerDelta(IResourceDelta.ADDED, resource, newMarker); changedMarkers(resource, changes); } /** * Returns the markers in the given set of markers which match the given type. */ protected MarkerInfo[] basicFindMatching(MarkerSet markers, String type, boolean includeSubtypes) { int size= markers.size(); if (size <= 0) return NO_MARKER_INFO; List result= new ArrayList(size); IMarkerSetElement[] elements= markers.elements(); for (int i= 0; i < elements.length; i++) { MarkerInfo marker= (MarkerInfo)elements[i]; // if the type is null then we are looking for all types of markers if (type == null) result.add(marker); else { if (includeSubtypes) { if (cache.isSubtype(marker.getType(), type)) result.add(marker); } else { if (marker.getType().equals(type)) result.add(marker); } } } size= result.size(); if (size <= 0) return NO_MARKER_INFO; return (MarkerInfo[])result.toArray(new MarkerInfo[size]); } protected int basicFindMaxSeverity(MarkerSet markers, String type, boolean includeSubtypes) { int max= -1; int size= markers.size(); if (size <= 0) return max; IMarkerSetElement[] elements= markers.elements(); for (int i= 0; i < elements.length; i++) { MarkerInfo marker= (MarkerInfo)elements[i]; // if the type is null then we are looking for all types of markers if (type == null) max= Math.max(max, getSeverity(marker)); else { if (includeSubtypes) { if (cache.isSubtype(marker.getType(), type)) max= Math.max(max, getSeverity(marker)); } else { if (marker.getType().equals(type)) max= Math.max(max, getSeverity(marker)); } } if (max >= IMarker.SEVERITY_ERROR) { break; } } return max; } private int getSeverity(MarkerInfo marker) { Object o= marker.getAttribute(IMarker.SEVERITY); if (o instanceof Integer) { Integer i= (Integer)o; return i.intValue(); } return -1; } /** * Removes markers of the specified type from the given resource. Note: this method is protected * to avoid creation of a synthetic accessor (it is called from an anonymous inner class). */ protected void basicRemoveMarkers(ResourceInfo info, IPathRequestor requestor, String type, boolean includeSubtypes) { MarkerSet markers= info.getMarkers(false); if (markers == null) return; IMarkerSetElement[] matching; IPath path; if (type == null) { // if the type is null, all markers are to be removed. //now we need to crack open the tree path= requestor.requestPath(); info= workspace.getResourceInfo(path, false, true); info.setMarkers(null); matching= markers.elements(); } else { matching= basicFindMatching(markers, type, includeSubtypes); // if none match, there is nothing to remove if (matching.length == 0) return; //now we need to crack open the tree path= requestor.requestPath(); info= workspace.getResourceInfo(path, false, true); //Concurrency: copy the marker set on modify markers= info.getMarkers(true); // remove all the matching markers and also the whole // set if there are no remaining markers markers.removeAll(matching); info.setMarkers(markers.size() == 0 ? null : markers); } info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); IMarkerSetElement[] changes= new IMarkerSetElement[matching.length]; IResource resource= workspace.getRoot().findMember(path); for (int i= 0; i < matching.length; i++) changes[i]= new MarkerDelta(IResourceDelta.REMOVED, resource, (MarkerInfo)matching[i]); changedMarkers(resource, changes); return; } /** * Adds the markers on the given target which match the specified type to the list. */ protected void buildMarkers(IMarkerSetElement[] markers, IPath path, int type, ArrayList list) { if (markers.length == 0) return; IResource resource= workspace.newResource(path, type); list.ensureCapacity(list.size() + markers.length); for (int i= 0; i < markers.length; i++) { list.add(new Marker(resource, ((MarkerInfo)markers[i]).getId())); } } /** * Markers have changed on the given resource. Remember the changes for subsequent notification. */ protected void changedMarkers(IResource resource, IMarkerSetElement[] changes) { if (changes == null || changes.length == 0) return; changeId++; if (currentDeltas == null) currentDeltas= deltaManager.newGeneration(changeId); IPath path= resource.getFullPath(); MarkerSet previousChanges= (MarkerSet)currentDeltas.get(path); MarkerSet result= MarkerDelta.merge(previousChanges, changes); if (result.size() == 0) currentDeltas.remove(path); else currentDeltas.put(path, result); ResourceInfo info= workspace.getResourceInfo(path, false, true); if (info != null) info.incrementMarkerGenerationCount(); } /** * Returns the marker with the given id or <code>null</code> if none is found. */ public IMarker findMarker(IResource resource, long id) { MarkerInfo info= findMarkerInfo(resource, id); return info == null ? null : new Marker(resource, info.getId()); } /** * Returns the marker with the given id or <code>null</code> if none is found. */ public MarkerInfo findMarkerInfo(IResource resource, long id) { ResourceInfo info= workspace.getResourceInfo(resource.getFullPath(), false, false); if (info == null) return null; MarkerSet markers= info.getMarkers(false); if (markers == null) return null; return (MarkerInfo)markers.get(id); } /** * Returns all markers of the specified type on the given target, with option to search the * target's children. Passing <code>null</code> for the type specifies a match for all types * (i.e., <code>null</code> is a wildcard. */ public IMarker[] findMarkers(IResource target, final String type, final boolean includeSubtypes, int depth) { ArrayList result= new ArrayList(); doFindMarkers(target, result, type, includeSubtypes, depth); if (result.size() == 0) return NO_MARKERS; return (IMarker[])result.toArray(new IMarker[result.size()]); } /** * Fills the provided list with all markers of the specified type on the given target, with * option to search the target's children. Passing <code>null</code> for the type specifies a * match for all types (i.e., <code>null</code> is a wildcard. */ public void doFindMarkers(IResource target, ArrayList result, final String type, final boolean includeSubtypes, int depth) { //optimize the deep searches with an element tree visitor if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE) visitorFindMarkers(target.getFullPath(), result, type, includeSubtypes); else recursiveFindMarkers(target.getFullPath(), result, type, includeSubtypes, depth); } /** * Finds the max severity across all problem markers on the given target, with option to search * the target's children. */ public int findMaxProblemSeverity(IResource target, String type, boolean includeSubtypes, int depth) { //optimize the deep searches with an element tree visitor if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE) return visitorFindMaxSeverity(target.getFullPath(), type, includeSubtypes); return recursiveFindMaxSeverity(target.getFullPath(), type, includeSubtypes, depth); } public long getChangeId() { return changeId; } /** * Returns the map of all marker deltas since the given change Id. */ public Map getMarkerDeltas(long startChangeId) { return deltaManager.assembleDeltas(startChangeId); } /** * Returns true if this manager has a marker delta record for the given marker id, and false * otherwise. */ boolean hasDelta(IPath path, long id) { if (currentDeltas == null) return false; MarkerSet set= (MarkerSet)currentDeltas.get(path); if (set == null) return false; return set.get(id) != null; } /** * Returns true if the given marker is persistent, and false otherwise. */ public boolean isPersistent(MarkerInfo info) { if (!cache.isPersistent(info.getType())) return false; Object isTransient= info.getAttribute(IMarker.TRANSIENT); return isTransient == null || !(isTransient instanceof Boolean) || !((Boolean)isTransient).booleanValue(); } /** * Returns true if the given marker type is persistent, and false otherwise. */ public boolean isPersistentType(String type) { return cache.isPersistent(type); } /** * Returns true if <code>type</code> is a sub type of <code>superType</code>. */ public boolean isSubtype(String type, String superType) { return cache.isSubtype(type, superType); } public void moved(final IResource source, final IResource destination, int depth) throws CoreException { final int count= destination.getFullPath().segmentCount(); // we removed from the source and added to the destination IResourceVisitor visitor= new IResourceVisitor() { public boolean visit(IResource resource) { Resource r= (Resource)resource; ResourceInfo info= r.getResourceInfo(false, true); MarkerSet markers= info.getMarkers(false); if (markers == null) return true; info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); IMarkerSetElement[] removed= new IMarkerSetElement[markers.size()]; IMarkerSetElement[] added= new IMarkerSetElement[markers.size()]; IPath path= resource.getFullPath().removeFirstSegments(count); path= source.getFullPath().append(path); IResource sourceChild= workspace.newResource(path, resource.getType()); IMarkerSetElement[] elements= markers.elements(); for (int i= 0; i < elements.length; i++) { // calculate the ADDED delta MarkerInfo markerInfo= (MarkerInfo)elements[i]; MarkerDelta delta= new MarkerDelta(IResourceDelta.ADDED, resource, markerInfo); added[i]= delta; // calculate the REMOVED delta delta= new MarkerDelta(IResourceDelta.REMOVED, sourceChild, markerInfo); removed[i]= delta; } changedMarkers(resource, added); changedMarkers(sourceChild, removed); return true; } }; destination.accept(visitor, depth, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); } /** * Adds the markers for a subtree of resources to the list. */ private void recursiveFindMarkers(IPath path, ArrayList list, String type, boolean includeSubtypes, int depth) { ResourceInfo info= workspace.getResourceInfo(path, false, false); if (info == null) return; MarkerSet markers= info.getMarkers(false); //add the matching markers for this resource if (markers != null) { IMarkerSetElement[] matching; if (type == null) matching= markers.elements(); else matching= basicFindMatching(markers, type, includeSubtypes); buildMarkers(matching, path, info.getType(), list); } //recurse if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE) return; if (depth == IResource.DEPTH_ONE) depth= IResource.DEPTH_ZERO; IPath[] children= workspace.getElementTree().getChildren(path); for (int i= 0; i < children.length; i++) { recursiveFindMarkers(children[i], list, type, includeSubtypes, depth); } } /** * Finds the max severity across problem markers for a subtree of resources. */ private int recursiveFindMaxSeverity(IPath path, String type, boolean includeSubtypes, int depth) { ResourceInfo info= workspace.getResourceInfo(path, false, false); if (info == null) return -1; MarkerSet markers= info.getMarkers(false); //add the matching markers for this resource int max= -1; if (markers != null) { max= basicFindMaxSeverity(markers, type, includeSubtypes); if (max >= IMarker.SEVERITY_ERROR) { return max; } } //recurse if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE) return max; if (depth == IResource.DEPTH_ONE) depth= IResource.DEPTH_ZERO; IPath[] children= workspace.getElementTree().getChildren(path); for (int i= 0; i < children.length; i++) { max= Math.max(max, recursiveFindMaxSeverity(children[i], type, includeSubtypes, depth)); if (max >= IMarker.SEVERITY_ERROR) { break; } } return max; } /** * Adds the markers for a subtree of resources to the list. */ private void recursiveRemoveMarkers(final IPath path, String type, boolean includeSubtypes, int depth) { ResourceInfo info= workspace.getResourceInfo(path, false, false); if (info == null)//phantoms don't have markers return; IPathRequestor requestor= new IPathRequestor() { public String requestName() { return path.lastSegment(); } public IPath requestPath() { return path; } }; basicRemoveMarkers(info, requestor, type, includeSubtypes); //recurse if (depth == IResource.DEPTH_ZERO || info.getType() == IResource.FILE) return; if (depth == IResource.DEPTH_ONE) depth= IResource.DEPTH_ZERO; IPath[] children= workspace.getElementTree().getChildren(path); for (int i= 0; i < children.length; i++) { recursiveRemoveMarkers(children[i], type, includeSubtypes, depth); } } /** * Removes the specified marker */ public void removeMarker(IResource resource, long id) { MarkerInfo markerInfo= findMarkerInfo(resource, id); if (markerInfo == null) return; ResourceInfo info= ((Workspace)resource.getWorkspace()).getResourceInfo(resource.getFullPath(), false, true); //Concurrency: copy the marker set on modify MarkerSet markers= info.getMarkers(true); int size= markers.size(); markers.remove(markerInfo); // if that was the last marker remove the set to save space. info.setMarkers(markers.size() == 0 ? null : markers); // if we actually did remove a marker, post a delta for the change. if (markers.size() != size) { if (isPersistent(markerInfo)) info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); IMarkerSetElement[] change= new IMarkerSetElement[] { new MarkerDelta(IResourceDelta.REMOVED, resource, markerInfo) }; changedMarkers(resource, change); } } /** * Remove all markers for the given resource to the specified depth. */ public void removeMarkers(IResource resource, int depth) { removeMarkers(resource, null, false, depth); } /** * Remove all markers with the given type from the node at the given path. Passing * <code>null</code> for the type specifies a match for all types (i.e., <code>null</code> is a * wildcard. */ public void removeMarkers(IResource target, final String type, final boolean includeSubtypes, int depth) { if (depth == IResource.DEPTH_INFINITE && target.getType() != IResource.FILE) visitorRemoveMarkers(target.getFullPath(), type, includeSubtypes); else recursiveRemoveMarkers(target.getFullPath(), type, includeSubtypes, depth); } /** * Reset the marker deltas up to but not including the given start Id. */ public void resetMarkerDeltas(long startId) { currentDeltas= null; deltaManager.resetDeltas(startId); } public void restore(IResource resource, boolean generateDeltas, IProgressMonitor monitor) throws CoreException { // first try and load the last saved file, then apply the snapshots restoreFromSave(resource, generateDeltas); restoreFromSnap(resource); } protected void restoreFromSave(IResource resource, boolean generateDeltas) throws CoreException { IPath sourceLocation= workspace.getMetaArea().getMarkersLocationFor(resource); IPath tempLocation= workspace.getMetaArea().getBackupLocationFor(sourceLocation); java.io.File sourceFile= new java.io.File(sourceLocation.toOSString()); java.io.File tempFile= new java.io.File(tempLocation.toOSString()); if (!sourceFile.exists() && !tempFile.exists()) return; try { DataInputStream input= new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString())); try { MarkerReader reader= new MarkerReader(workspace); reader.read(input, generateDeltas); } finally { input.close(); } } catch (Exception e) { //don't let runtime exceptions such as ArrayIndexOutOfBounds prevent startup String msg= NLS.bind(Messages.resources_readMeta, sourceLocation); throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e); } } protected void restoreFromSnap(IResource resource) { IPath sourceLocation= workspace.getMetaArea().getMarkersSnapshotLocationFor(resource); if (!sourceLocation.toFile().exists()) return; try { DataInputStream input= new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile())); try { MarkerSnapshotReader reader= new MarkerSnapshotReader(workspace); while (true) reader.read(input); } catch (EOFException eof) { // ignore end of file } finally { input.close(); } } catch (Exception e) { // only log the exception, we should not fail restoring the snapshot String msg= NLS.bind(Messages.resources_readMeta, sourceLocation); Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e)); } } public void save(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List list) throws IOException { writer.save(info, requestor, output, list); } /* (non-Javadoc) * @see IManager#shutdown(IProgressMonitor) */ public void shutdown(IProgressMonitor monitor) { // do nothing } public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { writer.snap(info, requestor, output); } /* (non-Javadoc) * @see IManager#startup(IProgressMonitor) */ public void startup(IProgressMonitor monitor) { // do nothing } /** * Adds the markers for a subtree of resources to the list. */ private void visitorFindMarkers(IPath path, final ArrayList list, final String type, final boolean includeSubtypes) { IElementContentVisitor visitor= new IElementContentVisitor() { public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { ResourceInfo info= (ResourceInfo)elementContents; if (info == null) return false; MarkerSet markers= info.getMarkers(false); //add the matching markers for this resource if (markers != null) { IMarkerSetElement[] matching; if (type == null) matching= markers.elements(); else matching= basicFindMatching(markers, type, includeSubtypes); buildMarkers(matching, requestor.requestPath(), info.getType(), list); } return true; } }; new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor); } /** * Finds the max severity across problem markers for a subtree of resources. */ private int visitorFindMaxSeverity(IPath path, final String type, final boolean includeSubtypes) { class MaxSeverityVisitor implements IElementContentVisitor { int max= -1; public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { // bail if an earlier sibling already hit the max if (max >= IMarker.SEVERITY_ERROR) { return false; } ResourceInfo info= (ResourceInfo)elementContents; if (info == null) return false; MarkerSet markers= info.getMarkers(false); //add the matching markers for this resource if (markers != null) { max= Math.max(max, basicFindMaxSeverity(markers, type, includeSubtypes)); } return max < IMarker.SEVERITY_ERROR; } } MaxSeverityVisitor visitor= new MaxSeverityVisitor(); new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor); return visitor.max; } /** * Adds the markers for a subtree of resources to the list. */ private void visitorRemoveMarkers(IPath path, final String type, final boolean includeSubtypes) { IElementContentVisitor visitor= new IElementContentVisitor() { public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { ResourceInfo info= (ResourceInfo)elementContents; if (info == null) return false; basicRemoveMarkers(info, requestor, type, includeSubtypes); return true; } }; new ElementTreeIterator(workspace.getElementTree(), path).iterate(visitor); } }