blob: 6f74d455cb1c51d9e6e47ff32849ad0565e164f5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 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.ui.ide.undo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.ui.internal.ide.undo.MarkerDescription;
import org.eclipse.ui.internal.ide.undo.UndoMessages;
/**
* An AbstractMarkersOperation represents an undoable operation that affects
* markers on a resource. It provides implementations for marker creation,
* deletion, and updating. Clients may call the public API from a background
* thread.
*
* This class is not intended to be subclassed by clients.
*
* @since 3.3
*
*/
abstract class AbstractMarkersOperation extends AbstractWorkspaceOperation {
MarkerDescription[] markerDescriptions;
IMarker[] markers;
Map[] attributes;
/**
* Create an AbstractMarkersOperation by specifying a combination of markers
* and attributes or marker descriptions.
*
* @param markers
* the markers used in the operation or <code>null</code> if no
* markers yet exist
* @param markerDescriptions
* the marker descriptions that should be used to create markers,
* or <code>null</code> if the markers already exist
* @param attributes
* The map of attributes that should be assigned to any existing
* markers when the markers are updated. Ignored if the markers
* parameter is <code>null</code>.
* @param name
* the name used to describe the operation
*/
AbstractMarkersOperation(IMarker[] markers,
MarkerDescription[] markerDescriptions, Map attributes, String name) {
super(name);
this.markers = markers;
this.attributes = null;
// If there is more than one marker, create an array with a copy
// of the attributes map for each marker. Keeping a unique map
// per marker allows us to support the scenario where attributes
// are merged when updated. In this case, each marker's attributes
// may differ since their original attributes may have differed.
if (attributes != null && markers != null) {
if (markers.length > 1) {
this.attributes = new Map[markers.length];
for (int i = 0; i < markers.length; i++) {
Map copiedAttributes = new HashMap();
copiedAttributes.putAll(attributes);
this.attributes[i] = copiedAttributes;
}
} else {
this.attributes = new Map[] { attributes };
}
}
setMarkerDescriptions(markerDescriptions);
}
/**
* Delete any currently known markers and save their information in marker
* descriptions so that they can be restored.
*
* @param work
* the number of work ticks to be used by the delete
* @param monitor
* the progress monitor to use for the delete
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*
*/
protected void deleteMarkers(int work, IProgressMonitor monitor)
throws CoreException {
if (markers == null || markers.length == 0) {
monitor.worked(work);
return;
}
int markerWork = work / markers.length;
markerDescriptions = new MarkerDescription[markers.length];
for (int i = 0; i < markers.length; i++) {
markerDescriptions[i] = new MarkerDescription(markers[i]);
markers[i].delete();
monitor.worked(markerWork);
}
markers = new IMarker[0];
}
/**
* Create markers from any currently known marker descriptions.
*
* @param work
* the number of work ticks to be used by the create
* @param monitor
* the progress monitor to use for the create
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*/
protected void createMarkers(int work, IProgressMonitor monitor)
throws CoreException {
if (markerDescriptions == null || markerDescriptions.length == 0) {
monitor.worked(work);
return;
}
int markerWork = work / markerDescriptions.length;
markers = new IMarker[markerDescriptions.length];
// Recreate the markers from the descriptions
for (int i = 0; i < markerDescriptions.length; i++) {
markers[i] = markerDescriptions[i].createMarker();
monitor.worked(markerWork);
}
}
/**
* Update the currently known markers with the corresponding array of marker
* descriptions.
*
* @param work
* the number of work ticks to be used by the update
* @param monitor
* the progress monitor to use for the update
* @param mergeAttributes
* a boolean specifying whether the attributes are merged or
* considered to be a replacement of the previous attributes.
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*
*/
protected void updateMarkers(int work, IProgressMonitor monitor,
boolean mergeAttributes) throws CoreException {
if (attributes == null || markers == null
|| attributes.length != markers.length || markers.length == 0) {
monitor.worked(work);
return;
}
int markerWork = work / markers.length;
for (int i = 0; i < markers.length; i++) {
if (mergeAttributes) {
Map oldAttributes = markers[i].getAttributes();
int increment = markerWork / attributes[i].size();
Map replacedAttributes = new HashMap();
for (Iterator iter = attributes[i].keySet().iterator(); iter
.hasNext();) {
String key = (String) iter.next();
Object val = attributes[i].get(key);
markers[i].setAttribute(key, val);
replacedAttributes.put(key, oldAttributes.get(key));
monitor.worked(increment);
}
attributes[i] = replacedAttributes;
} else {
// replace all of the attributes
Map oldAttributes = markers[i].getAttributes();
markers[i].setAttributes(attributes[i]);
attributes[i] = oldAttributes;
}
}
}
/**
* Set the marker descriptions that describe markers that can be created.
*
* @param descriptions
* the descriptions of markers that can be created.
*/
protected void setMarkerDescriptions(MarkerDescription[] descriptions) {
markerDescriptions = descriptions;
addUndoContexts();
updateTargetResources();
}
/*
* Update the target resources by traversing the currently known markers or
* marker descriptions and getting their resources.
*/
private void updateTargetResources() {
IResource[] resources = null;
if (markers == null) {
if (markerDescriptions != null) {
resources = new IResource[markerDescriptions.length];
for (int i = 0; i < markerDescriptions.length; i++) {
resources[i] = markerDescriptions[i].getResource();
}
}
} else {
resources = new IResource[markers.length];
for (int i = 0; i < markers.length; i++) {
resources[i] = markers[i].getResource();
}
}
setTargetResources(resources);
}
/*
* Add undo contexts according to marker types. Any unknown marker types
* will cause the workspace undo context to be added.
*
* This is an optimization that allows us to add specific undo contexts for
* tasks and bookmarks, without also adding the workspace undo context. Note
* that clients with different marker types may still assign their own
* specific undo context using AbstractOperation.addContext(IUndoContext) in
* addition to the workspace context assigned by this method.
*/
private void addUndoContexts() {
String[] types = null;
if (markers == null) {
if (markerDescriptions != null) {
types = new String[markerDescriptions.length];
for (int i = 0; i < markerDescriptions.length; i++) {
types[i] = markerDescriptions[i].getType();
}
}
} else {
types = new String[markers.length];
for (int i = 0; i < markers.length; i++) {
try {
types[i] = markers[i].getType();
} catch (CoreException e) {
}
}
}
if (types != null) {
for (int i = 0; i < types.length; i++) {
// Marker type could be null if marker did not exist.
// This shouldn't happen, but can.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=158129
if (types[i] != null) {
if (types[i].equals(IMarker.BOOKMARK)) {
addContext(WorkspaceUndoUtil.getBookmarksUndoContext());
} else if (types[i].equals(IMarker.TASK)) {
addContext(WorkspaceUndoUtil.getTasksUndoContext());
} else if (types[i] != null) {
// type is not known, use the workspace undo context
addContext(WorkspaceUndoUtil.getWorkspaceUndoContext());
}
}
}
}
}
/**
* Return the array of markers that has been updated or created.
*
* @return the array of markers that have been updated or created, or
* <code>null</code> if no markers have been created or updated.
*/
public IMarker[] getMarkers() {
return markers;
}
/**
* Return whether the markers known by this operation currently exist.
*
* @return <code>true</code> if there are existing markers and
* <code>false</code> if there are no known markers or any one of
* them does not exist
*/
protected boolean markersExist() {
if (markers == null || markers.length == 0) {
return false;
}
for (int i = 0; i < markers.length; i++) {
if (!markers[i].exists()) {
return false;
}
}
return true;
}
/**
* Return a status indicating the projected outcome of undoing the marker
* operation. The receiver is not responsible for remembering the result of
* this computation.
*
* @return the status indicating whether the operation can be undone
*/
protected abstract IStatus getBasicUndoStatus();
/**
* Return a status indicating the projected outcome of redoing the marker
* operation. The receiver is not responsible for remembering the result of
* this computation.
*
* @return the status indicating whether the operation can be undone
*/
protected abstract IStatus getBasicRedoStatus();
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeExecutionStatus(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus computeExecutionStatus(IProgressMonitor monitor) {
IStatus status = getBasicRedoStatus();
if (status.isOK()) {
return super.computeExecutionStatus(monitor);
}
if (status.getSeverity() == IStatus.ERROR) {
markInvalid();
}
return status;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeUndoableStatus(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus computeUndoableStatus(IProgressMonitor monitor) {
IStatus status = getBasicUndoStatus();
if (status.isOK()) {
return super.computeUndoableStatus(monitor);
}
if (status.getSeverity() == IStatus.ERROR) {
markInvalid();
}
return status;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeRedoableStatus(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus computeRedoableStatus(IProgressMonitor monitor) {
IStatus status = getBasicRedoStatus();
if (status.isOK()) {
return super.computeRedoableStatus(monitor);
}
if (status.getSeverity() == IStatus.ERROR) {
markInvalid();
}
return status;
}
/**
* Compute the status for deleting any known markers. A status severity of
* <code>OK</code> indicates that the delete is likely to be successful. A
* status severity of <code>ERROR</code> indicates that the operation is
* no longer valid. Other status severities are open to interpretation by
* the caller.
*
* @return the status indicating the projected outcome of deleting the
* markers.
*
*/
protected IStatus getMarkerDeletionStatus() {
if (markersExist()) {
return Status.OK_STATUS;
}
return getErrorStatus(UndoMessages.MarkerOperation_MarkerDoesNotExist);
}
/**
* Compute the status for creating any known markers. A status severity of
* <code>OK</code> indicates that the create is likely to be successful. A
* status severity of <code>ERROR</code> indicates that the operation is
* no longer valid. Other status severities are open to interpretation by
* the caller.
*
* @return the status indicating the projected outcome of creating the
* markers.
*
*/
protected IStatus getMarkerCreationStatus() {
if (!resourcesExist()) {
return getErrorStatus(UndoMessages.MarkerOperation_ResourceDoesNotExist);
} else if (markerDescriptions == null) {
return getErrorStatus(UndoMessages.MarkerOperation_NotEnoughInfo);
}
return Status.OK_STATUS;
}
/**
* Compute the status for updating any known markers. A status severity of
* <code>OK</code> indicates that the update is likely to be successful. A
* status severity of <code>ERROR</code> indicates that the operation is
* no longer valid. Other status severities are open to interpretation by
* the caller.
*
* @return the status indicating the projected outcome of updating the
* markers.
*
*/
protected IStatus getMarkerUpdateStatus() {
if (!markersExist()) {
return getErrorStatus(UndoMessages.MarkerOperation_MarkerDoesNotExist);
} else if (attributes == null) {
return getErrorStatus(UndoMessages.MarkerOperation_NotEnoughInfo);
}
return Status.OK_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#getExecuteSchedulingRule()
*/
protected ISchedulingRule getExecuteSchedulingRule() {
ISchedulingRule[] ruleArray = new ISchedulingRule[resources.length];
for (int i = 0; i < resources.length; i++) {
ruleArray[i] = getWorkspaceRuleFactory().markerRule(resources[i]);
}
return MultiRule.combine(ruleArray);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#getUndoSchedulingRule()
*/
protected ISchedulingRule getUndoSchedulingRule() {
return getExecuteSchedulingRule();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#appendDescriptiveText(java.lang.StringBuffer)
*/
protected void appendDescriptiveText(StringBuffer text) {
super.appendDescriptiveText(text);
text.append(" markers: "); //$NON-NLS-1$
text.append(markers);
text.append('\'');
text.append(" markerDescriptions: "); //$NON-NLS-1$
text.append(markerDescriptions);
text.append('\'');
text.append(" attributes: "); //$NON-NLS-1$
text.append(attributes);
text.append('\'');
}
}