package net.sourceforge.pmd.eclipse.ui.views.actions;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Date;
import net.sourceforge.pmd.eclipse.plugin.PMDPlugin;
import net.sourceforge.pmd.eclipse.runtime.PMDRuntimeConstants;
import net.sourceforge.pmd.eclipse.runtime.builder.MarkerUtil;
import net.sourceforge.pmd.eclipse.ui.PMDUiConstants;
import net.sourceforge.pmd.eclipse.ui.nls.StringKeys;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
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.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelStatus;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
/**
* Mark a violation as reviewed
*
* @author Philippe Herlin
*
*/
public class ReviewAction extends AbstractViolationSelectionAction {
private static final Logger log = Logger.getLogger(ReviewAction.class);
private IProgressMonitor monitor;
/**
* Constructor
*/
public ReviewAction(TableViewer viewer) {
super(viewer);
}
protected String textId() { return StringKeys.VIEW_ACTION_REVIEW; }
protected String imageId() { return PMDUiConstants.ICON_BUTTON_REVIEW; }
protected String tooltipMsgId() { return StringKeys.VIEW_TOOLTIP_REVIEW; }
/**
* @see org.eclipse.jface.action.IAction#run()
*/
public void run() {
final IMarker[] markers = getSelectedViolations();
if (markers == null) return;
final boolean reviewPmdStyle = loadPreferences().isReviewPmdStyleEnabled();
// Get confirmation if multiple markers are selected
// Not necessary when using PMD style
boolean go = confirmForMultiples(markers, reviewPmdStyle);
// If only one marker selected or user has confirmed, review violation
if (go) {
try {
ProgressMonitorDialog dialog = new ProgressMonitorDialog(Display.getCurrent().getActiveShell());
dialog.run(false, false, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
setMonitor(monitor);
monitor.beginTask(getString(StringKeys.MONITOR_REVIEW), 5);
insertReview(markers[0], reviewPmdStyle);
monitor.done();
}
});
} catch (InvocationTargetException e) {
logErrorByKey(StringKeys.ERROR_INVOCATIONTARGET_EXCEPTION, e);
} catch (InterruptedException e) {
logErrorByKey(StringKeys.ERROR_INTERRUPTED_EXCEPTION, e);
}
}
}
private static boolean confirmForMultiples(IMarker[] markers, boolean reviewPmdStyle) {
boolean go = true;
if (markers.length > 1 && !reviewPmdStyle) {
String title = getString(StringKeys.CONFIRM_TITLE);
String message = getString(StringKeys.CONFIRM_REVIEW_MULTIPLE_MARKERS);
Shell shell = Display.getCurrent().getActiveShell();
go = MessageDialog.openConfirm(shell, title, message);
}
return go;
}
/**
* Do the insertion of the review comment
*
* @param marker
*/
protected void insertReview(IMarker marker, boolean reviewPmdStyle) {
try {
IResource resource = marker.getResource();
if (resource instanceof IFile) {
IFile file = (IFile) resource;
if (file.exists()) {
String sourceCode = readFile(file);
monitorWorked();
int offset = getMarkerLineStart(sourceCode, marker.getAttribute(IMarker.LINE_NUMBER, 0));
monitorWorked();
sourceCode = reviewPmdStyle ?
addPmdReviewComment(sourceCode, offset, marker) :
addPluginReviewComment(sourceCode, offset, marker);
monitorWorked();
file.setContents(new ByteArrayInputStream(sourceCode.getBytes(file.getCharset())), false, true, getMonitor());
monitorWorked();
} else {
MessageDialog.openError(Display.getCurrent().getActiveShell(), getString(StringKeys.ERROR_TITLE),
"The file " + file.getName()
+ " doesn't exist, review aborted. Try to refresh the workspace and retry.");
}
}
} catch (JavaModelException jme) {
ignore(jme);
} catch (CoreException e) {
logErrorByKey(StringKeys.ERROR_CORE_EXCEPTION, e);
} catch (IOException e) {
logErrorByKey(StringKeys.ERROR_IO_EXCEPTION, e);
}
}
private static void ignore(JavaModelException jme) {
IJavaModelStatus status = jme.getJavaModelStatus();
PMDPlugin.getDefault().logError(status);
log.warn("Ignoring Java Model Exception : " + status.getMessage());
if (log.isDebugEnabled()) {
log.debug(" code : " + status.getCode());
log.debug(" severity : " + status.getSeverity());
IJavaElement[] elements = status.getElements();
for (int i = 0; i < elements.length; i++) {
log.debug(" element : " + elements[i].getElementName() + " (" + elements[i].getElementType() + ')');
}
}
}
/**
* Get the monitor
*
* @return
*/
protected IProgressMonitor getMonitor() {
return monitor;
}
/**
* Set the monitor
*
* @param monitor
*/
protected void setMonitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
/**
* Progress monitor
*/
private void monitorWorked() {
if (getMonitor() != null) {
getMonitor().worked(1);
}
}
/**
* Renvoie la position dans le code source du début de la ligne du marqueur
*/
private static int getMarkerLineStart(String sourceCode, int lineNumber) {
int lineStart;
int currentLine = 1;
for (lineStart = 0; lineStart < sourceCode.length(); lineStart++) {
if (currentLine == lineNumber) {
break;
} else {
if (sourceCode.charAt(lineStart) == '\n') {
currentLine++;
}
}
}
if (sourceCode.charAt(lineStart) == '\r') {
lineStart++;
}
return lineStart;
}
public static String additionalCommentTxt() {
String additionalCommentPattern = loadPreferences().getReviewAdditionalComment();
return MessageFormat.format(
additionalCommentPattern,
new Object[] { System.getProperty("user.name", ""), new Date() }
);
}
/**
* Insert a review comment with the Plugin style
*/
private static String addPluginReviewComment(String sourceCode, int offset, IMarker marker) {
// Copy the source code until the violation line not included
StringBuilder sb = new StringBuilder(sourceCode.substring(0, offset));
// Add the review comment
sb.append(computeIndent(sourceCode, offset));
sb.append(PMDRuntimeConstants.PLUGIN_STYLE_REVIEW_COMMENT);
sb.append(MarkerUtil.ruleNameFor(marker));
sb.append(": ").append(additionalCommentTxt());
sb.append(System.getProperty("line.separator"));
// Copy the rest of the source code
sb.append(sourceCode.substring(offset));
return sb.toString();
}
/**
* Insert a review comment with the PMD style
*/
private static String addPmdReviewComment(String sourceCode, int offset, IMarker marker) {
String result = sourceCode;
// Find the end of line
int index = sourceCode.substring(offset).indexOf('\r');
if (index == -1) {
index = sourceCode.substring(offset).indexOf('\n');
if (index == -1) {
index = sourceCode.substring(offset).length();
}
}
index += offset;
// Insert comment only if it does not already exist
if (sourceCode.substring(offset, index).indexOf(PMDRuntimeConstants.PMD_STYLE_REVIEW_COMMENT) == -1) {
// Copy the source code until the violation line included
StringBuilder sb = new StringBuilder(sourceCode.substring(0, index));
// Add the review comment
sb.append(' ').append(PMDRuntimeConstants.PMD_STYLE_REVIEW_COMMENT);
sb.append(' ').append(additionalCommentTxt());
// Copy the rest of the code
sb.append(sourceCode.substring(index));
result = sb.toString();
}
return result;
}
/**
* Calcul l'indentation employée sur la ligne du marker
*/
private static String computeIndent(String sourceCode, int offset) {
StringBuilder indent = new StringBuilder();
int i = 0;
while (Character.isWhitespace(sourceCode.charAt(offset + i))) {
indent.append(sourceCode.charAt(offset + i));
i++;
}
return indent.toString();
}
public static String readFile(IFile file) throws IOException, CoreException {
InputStream contents = file.getContents(true);
String charset = file.getCharset();
InputStreamReader reader = new InputStreamReader(contents, charset);
try {
char[] buffer = new char[4096];
StringBuilder sb = new StringBuilder(4096);
while (reader.ready()) {
int readCount = reader.read(buffer);
if (readCount != -1) {
sb.append(buffer, 0, readCount);
}
}
return sb.toString();
} finally {
reader.close();
}
}
}