/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on 11/09/2005
*/
package org.python.pydev.shared_ui.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.AbstractMarkerAnnotationModel;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.utils.ArrayUtils;
/**
* Helper class to deal with markers.
*
* It's main use is to replace the markers in a given resource for another set of markers.
*
* @author Fabio
*/
public class PyMarkerUtils {
/**
* This class represents the information to create a marker.
*
* @author Fabio
*/
public static class MarkerInfo {
public IDocument doc;
public String message;
public String markerType;
public int severity;
public boolean userEditable;
public boolean isTransient;
public int lineStart;
public int colStart;
public int lineEnd;
public int absoluteStart = -1;
public int absoluteEnd = -1;
public int colEnd;
public Map<String, Object> additionalInfo;
/**
* Constructor passing lines and relative positions
*/
public MarkerInfo(IDocument doc, String message, String markerType, int severity, boolean userEditable,
boolean isTransient, int lineStart, int colStart, int lineEnd, int colEnd,
Map<String, Object> additionalInfo) {
super();
this.doc = doc;
this.message = message;
this.markerType = markerType;
this.severity = severity;
this.userEditable = userEditable;
this.isTransient = isTransient;
this.lineStart = lineStart;
this.colStart = colStart;
this.lineEnd = lineEnd;
this.colEnd = colEnd;
this.additionalInfo = additionalInfo;
}
/**
* Constructor passing absolute position
*/
public MarkerInfo(IDocument doc, String message, String markerType, int severity, boolean userEditable,
boolean isTransient, int line, int absoluteStart, int absoluteEnd, Map<String, Object> additionalInfo) {
super();
this.doc = doc;
this.message = message;
this.markerType = markerType;
this.severity = severity;
this.userEditable = userEditable;
this.isTransient = isTransient;
this.lineStart = line;
this.lineEnd = line;
this.absoluteStart = absoluteStart;
this.absoluteEnd = absoluteEnd;
this.additionalInfo = additionalInfo;
}
/**
* @return a map with the properties to be set in the marker or null if some error happened while doing it.
* @throws BadLocationException
*/
private HashMap<String, Object> getAsMap() {
if (lineStart < 0) {
lineStart = 0;
}
if (absoluteStart == -1 || absoluteEnd == -1) {
//if the absolute wasn't specified, let's calculate it
IRegion start;
try {
start = doc.getLineInformation(lineStart);
} catch (BadLocationException e) {
//Don't log it. Just return null -- this happens because there's a delay from calculating things
//to actually using them and the document might have changed and the given line is no longer available
//(a new request should fix this)
return null;
} catch (Exception e) {
Log.log(IStatus.ERROR, "Could not get line: " + lineStart + " to add message: " + message, e);
return null;
}
try {
absoluteStart = start.getOffset() + colStart;
if (lineEnd >= 0 && colEnd >= 0) {
IRegion end = doc.getLineInformation(lineEnd);
absoluteEnd = end.getOffset() + colEnd;
} else {
//ok, we have to calculate it based on the line contents...
String line = doc.get(start.getOffset(), start.getLength());
int i;
FastStringBuffer buffer;
if ((i = line.indexOf('#')) != -1) {
buffer = new FastStringBuffer(line.substring(0, i), 0);
} else {
buffer = new FastStringBuffer(line, 0);
}
while (buffer.length() > 0 && Character.isWhitespace(buffer.lastChar())) {
buffer.deleteLast();
}
absoluteEnd = start.getOffset() + buffer.length();
}
} catch (BadLocationException e) {
//Don't log it. Just return null -- this happens because there's a delay from calculating things
//to actually using them and the document might have changed and the given line is no longer available
//(a new request should fix this and create the markers correctly because of the change in the document)
return null;
} catch (Exception e) {
Log.log(IStatus.INFO, "Problem creating map for:" + this.toString(), e);
return null;
}
}
HashMap<String, Object> map = new HashMap<String, Object>();
map.put(IMarker.MESSAGE, message);
map.put(IMarker.LINE_NUMBER, lineStart);
map.put(IMarker.CHAR_START, absoluteStart);
map.put(IMarker.CHAR_END, absoluteEnd);
map.put(IMarker.SEVERITY, severity);
map.put(IMarker.USER_EDITABLE, userEditable);
map.put(IMarker.TRANSIENT, isTransient);
if (additionalInfo != null) {
map.putAll(additionalInfo);
}
return map;
}
/**
* Constructs a <code>String</code> with all attributes
* in name = value format.
*
* @return a <code>String</code> representation
* of this object.
*/
@Override
public String toString() {
final String NL = "\n";
StringBuffer retValue = new StringBuffer();
retValue.append("MarkerInfo (\n").append("doc = ").append(this.doc).append(NL).append("message = ")
.append(this.message).append(NL).append("markerType = ").append(this.markerType).append(NL)
.append("severity = ").append(this.severity).append(NL).append("userEditable = ")
.append(this.userEditable).append(NL).append("isTransient = ").append(this.isTransient).append(NL)
.append("lineStart = ").append(this.lineStart).append(NL).append("colStart = ")
.append(this.colStart).append(NL).append("lineEnd = ").append(this.lineEnd).append(NL)
.append("absoluteStart = ").append(this.absoluteStart).append(NL).append("absoluteEnd = ")
.append(this.absoluteEnd).append(NL).append("colEnd = ").append(this.colEnd).append(NL)
.append("additionalInfo = ").append(this.additionalInfo).append(NL).append(")");
return retValue.toString();
}
}
/**
* This method allows clients to replace the existing markers of some type in a given resource for other markers.
*
* @param lst the new markers to be set in the resource
* @param resource the resource were the markers should be replaced
* @param markerType the type of the marker that'll be replaced
* @param removeUserEditable if true, will remove the user-editable markers too (otherwise, will leave the user-editable markers)
* @param monitor used to check whether this process should be canceled.
*/
@SuppressWarnings("unchecked")
public static void replaceMarkers(final List<MarkerInfo> lst, final IResource resource, final String markerType,
final boolean removeUserEditable, IProgressMonitor monitor) {
List<Map<String, Object>> lMap = new ArrayList<Map<String, Object>>(lst.size());
for (MarkerInfo markerInfo : lst) {
try {
HashMap<String, Object> asMap = markerInfo.getAsMap();
if (asMap != null) {
lMap.add(asMap);
}
} catch (Exception e) {
Log.log(e);
}
}
replaceMarkers(lMap.toArray(new Map[lMap.size()]), resource, markerType,
removeUserEditable, monitor);
}
/**
* This method allows clients to replace the existing markers of some type in a given resource for other markers.
*
* @param lst the new markers to be set in the resource
* @param resource the resource were the markers should be replaced
* @param markerType the type of the marker that'll be replaced
* @param removeUserEditable if true, will remove the user-editable markers too (otherwise, will leave the user-editable markers)
* @param monitor used to check whether this process should be canceled.
*/
public static void replaceMarkers(final Map<String, Object>[] lst, final IResource resource,
final String markerType,
final boolean removeUserEditable, IProgressMonitor monitor) {
IWorkspaceRunnable r = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
if (!resource.exists()) {
return;
}
try {
if (removeUserEditable) {
resource.deleteMarkers(markerType, true, IResource.DEPTH_ZERO);
} else {
IMarker[] existingMarkers;
existingMarkers = resource.findMarkers(markerType, false, IResource.DEPTH_ZERO);
//we don't want to remove the user-editable markers, so, let's filter them out!
existingMarkers = ArrayUtils.filter(existingMarkers, new ICallback<Boolean, IMarker>() {
@Override
public Boolean call(IMarker marker) {
//if it's user-editable, it should not be included in the list
return !marker.getAttribute(IMarker.USER_EDITABLE, true); //default for user-editable is true.
}
}).toArray(new IMarker[0]);
ResourcesPlugin.getWorkspace().deleteMarkers(existingMarkers);
}
} catch (Exception e1) {
Log.log(e1);
}
try {
for (Map<String, Object> asMap : lst) {
IMarker marker = resource.createMarker(markerType);
marker.setAttributes(asMap);
}
} catch (Exception e) {
Log.log(e);
}
}
};
try {
resource.getWorkspace().run(r, ResourcesPlugin.getWorkspace().getRuleFactory().markerRule(resource),
IWorkspace.AVOID_UPDATE, monitor);
} catch (Exception e) {
Log.log(e);
}
}
/**
* @param original
* @param pydevCoverageMarker
*/
public static void removeMarkers(IResource resource, String markerType) {
if (resource == null) {
return;
}
try {
resource.deleteMarkers(markerType, false, IResource.DEPTH_ZERO);
} catch (Exception e) {
Log.log(e);
}
}
/**
* @return the position for a marker.
*/
public static Position getMarkerPosition(IDocument document, IMarker marker, IAnnotationModel model) {
if (model instanceof AbstractMarkerAnnotationModel) {
Position ret = ((AbstractMarkerAnnotationModel) model).getMarkerPosition(marker);
if (ret != null) {
return ret;
}
}
int start = MarkerUtilities.getCharStart(marker);
int end = MarkerUtilities.getCharEnd(marker);
if (start > end) {
end = start + end;
start = end - start;
end = end - start;
}
if (start == -1 && end == -1) {
// marker line number is 1-based
int line = MarkerUtilities.getLineNumber(marker);
if (line > 0 && document != null) {
try {
start = document.getLineOffset(line - 1);
end = start;
} catch (BadLocationException x) {
}
}
}
if (start > -1 && end > -1) {
return new Position(start, end - start);
}
return null;
}
/**
* @return the resource for which to create the marker or <code>null</code>
*
* If the editor maps to a workspace file, it will return that file. Otherwise, it will return the
* workspace root (so, markers from external files will be created in the workspace root).
*/
public static IResource getResourceForTextEditor(ITextEditor textEditor) {
IEditorInput input = textEditor.getEditorInput();
IResource resource = input.getAdapter(IFile.class);
if (resource == null) {
resource = input.getAdapter(IResource.class);
}
if (resource == null) {
resource = ResourcesPlugin.getWorkspace().getRoot();
}
return resource;
}
public static boolean showToUser(IMarker m) {
if (m == null) {
return false;
}
Object msg;
try {
msg = m.getAttribute(IMarker.MESSAGE);
} catch (CoreException e) {
Log.log(e);
return false;
}
if (msg == null) {
return false;
}
Object severity;
try {
severity = m.getAttribute(IMarker.SEVERITY);
} catch (CoreException e) {
Log.log(e);
return false;
}
if (severity == null || !(severity instanceof Integer) | ((int) severity) < 0) {
return false;
}
if ("PyDev breakpoint".equals(msg)) {
return false;
}
return true;
}
}