//============================================================================ // // Copyright (C) 2002-2016 David Schneider, Lars Ködderitzsch // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //============================================================================ package net.sf.eclipsecs.core.builder; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.api.AuditEvent; import com.puppycrawl.tools.checkstyle.api.AuditListener; import com.puppycrawl.tools.checkstyle.api.CheckstyleException; import com.puppycrawl.tools.checkstyle.api.SeverityLevel; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.eclipsecs.core.CheckstylePluginPrefs; import net.sf.eclipsecs.core.Messages; import net.sf.eclipsecs.core.config.ICheckConfiguration; import net.sf.eclipsecs.core.config.Module; import net.sf.eclipsecs.core.config.meta.MetadataFactory; import net.sf.eclipsecs.core.config.meta.RuleMetadata; import net.sf.eclipsecs.core.util.CheckstyleLog; import net.sf.eclipsecs.core.util.CheckstylePluginException; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBufferManager; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.osgi.util.NLS; /** * Performs checking on Java source code. */ public class Auditor { /** The interval for updating the task info. */ private static final int MONITOR_INTERVAL = 10; /** The check configuration the auditor uses. */ private final ICheckConfiguration mCheckConfiguration; /** The progress monitor. */ private IProgressMonitor mMonitor; /** Map containing the file resources to audit. */ private final Map<String, IFile> mFiles = new HashMap<>(); /** Add the check rule name to the message. */ private boolean mAddRuleName = false; /** Add the check module id to the message. */ private boolean mAddModuleId = false; /** Reference to the file buffer manager. */ private final ITextFileBufferManager mFileBufferManager = FileBuffers.getTextFileBufferManager(); /** * Creates an auditor. * * @param checkConfiguration * the check configuraton to use during audit. */ public Auditor(ICheckConfiguration checkConfiguration) { mCheckConfiguration = checkConfiguration; // // check wether to include rule names and/or module id // mAddRuleName = CheckstylePluginPrefs.getBoolean(CheckstylePluginPrefs.PREF_INCLUDE_RULE_NAMES); mAddModuleId = CheckstylePluginPrefs.getBoolean(CheckstylePluginPrefs.PREF_INCLUDE_MODULE_IDS); } /** * Runs the audit on the files associated with the auditor. * * @param project * the project is needed to build the correct classpath for the checker * @param monitor * the progress monitor * @throws CheckstylePluginException * error processing the audit */ public void runAudit(IProject project, IProgressMonitor monitor) throws CheckstylePluginException { // System.out.println("----> Auditing: " + mFiles.size()); // skip if there are no files to check if (mFiles.isEmpty() || project == null) { return; } mMonitor = monitor; Checker checker = null; CheckstyleAuditListener listener = null; try { List<File> filesToAudit = getFilesList(); // begin task monitor.beginTask(NLS.bind(Messages.Auditor_msgCheckingConfig, mCheckConfiguration.getName()), filesToAudit.size()); // create checker checker = CheckerFactory.createChecker(mCheckConfiguration, project); // create and add listener listener = new CheckstyleAuditListener(project); checker.addListener(listener); // run the files through the checker checker.process(filesToAudit); } catch (CheckstyleException e) { if (e.getCause() instanceof OperationCanceledException) { // user requested cancellation, keep silent } else { handleCheckstyleFailure(project, e); } } catch (RuntimeException e) { if (listener != null) { listener.cleanup(); } throw e; } finally { monitor.done(); // Cleanup listener and filter if (checker != null) { checker.removeListener(listener); } } } private void handleCheckstyleFailure(IProject project, CheckstyleException e) throws CheckstylePluginException { try { CheckstyleLog.log(e); // remove pre-existing project level marker project.deleteMarkers(CheckstyleMarker.MARKER_ID, false, IResource.DEPTH_ZERO); Map<String, Object> attrs = new HashMap<>(); attrs.put(IMarker.PRIORITY, Integer.valueOf(IMarker.PRIORITY_NORMAL)); attrs.put(IMarker.SEVERITY, Integer.valueOf(IMarker.SEVERITY_ERROR)); attrs.put(IMarker.MESSAGE, NLS.bind(Messages.Auditor_msgMsgCheckstyleInternalError, null)); IMarker projectMarker = project.createMarker(CheckstyleMarker.MARKER_ID); projectMarker.setAttributes(attrs); } catch (CoreException ce) { CheckstylePluginException.rethrow(e); } } /** * Add a file to the audit. * * @param file * the file */ public void addFile(IFile file) { mFiles.put(file.getLocation().toString(), file); } /** * Get a file resource by the file name. * * @param fileName * the file name * @return the file resource or <code>null</code> */ private IFile getFile(String fileName) { return mFiles.get(new Path(fileName).toString()); } /** * Helper method to get an array of java.io.Files. This array gets passed to the checker. * * @return */ private List<File> getFilesList() { List<File> files = new ArrayList<>(); for (IFile file : mFiles.values()) { files.add(file.getLocation().toFile()); } return files; } /** * Implementation of the audit listener. This listener creates markers on the file resources if * checkstyle messages are reported. * * @author David Schneider * @author Lars Ködderitzsch */ private class CheckstyleAuditListener implements AuditListener { /** the project. */ private final IProject mProject; /** The file currently being checked. */ private IResource mResource; /** Document model of the current file. */ private IDocument mDocument; /** internal counter used to time to actualisation of the monitor. */ private int mMonitorCounter; /** map containing the marker data. */ private final Map<String, Object> mMarkerAttributes = new HashMap<>(); /** flags if the amount of markers should be limited. */ private final boolean mLimitMarkers; /** the max amount of markers per resource. */ private final int mMarkerLimit; /** the count of markers generated for the current resource. */ private int mMarkerCount; /** * keep track which file paths have been connected with the BufferManager. */ private Set<IPath> mConnectedFileBufferPaths = new HashSet<>(); public CheckstyleAuditListener(IProject project) { mProject = project; // init the marker limitation mLimitMarkers = CheckstylePluginPrefs .getBoolean(CheckstylePluginPrefs.PREF_LIMIT_MARKERS_PER_RESOURCE); mMarkerLimit = CheckstylePluginPrefs.getInt(CheckstylePluginPrefs.PREF_MARKER_AMOUNT_LIMIT); } @Override public void fileStarted(AuditEvent event) { if (mMonitor.isCanceled()) { throw new OperationCanceledException(); } // get the current IFile reference mResource = getFile(event.getFileName()); mMarkerCount = 0; if (mResource != null) { // begin subtask if (mMonitorCounter == 0) { mMonitor.subTask(NLS.bind(Messages.Auditor_msgCheckingFile, mResource.getName())); } // increment monitor-counter this.mMonitorCounter++; } else { IPath filePath = new Path(event.getFileName()); IPath dirPath = filePath.removeFileExtension().removeLastSegments(1); IPath projectPath = mProject.getLocation(); if (projectPath.isPrefixOf(dirPath)) { // find the resource with a project relative path mResource = mProject.findMember(dirPath.removeFirstSegments(projectPath.segmentCount())); } else { // if the resource is not inside the project, take project // as resource - this should not happen mResource = mProject; } } } @Override public void addError(AuditEvent error) { try { if (!mLimitMarkers || mMarkerCount < mMarkerLimit) { SeverityLevel severity = error.getSeverityLevel(); if (!severity.equals(SeverityLevel.IGNORE) && mResource != null) { RuleMetadata metaData = MetadataFactory.getRuleMetadata(error.getSourceName()); // create generic metadata if none can be found if (metaData == null) { Module module = new Module(error.getSourceName()); metaData = MetadataFactory.createGenericMetadata(module); } mMarkerAttributes.put(CheckstyleMarker.MODULE_NAME, metaData.getInternalName()); mMarkerAttributes.put(CheckstyleMarker.MESSAGE_KEY, error.getLocalizedMessage().getKey()); mMarkerAttributes.put(IMarker.PRIORITY, Integer.valueOf(IMarker.PRIORITY_NORMAL)); mMarkerAttributes.put(IMarker.SEVERITY, Integer.valueOf(getSeverityValue(severity))); mMarkerAttributes.put(IMarker.LINE_NUMBER, Integer.valueOf(error.getLine())); mMarkerAttributes.put(IMarker.MESSAGE, getMessage(error)); // calculate offset for editor annotations calculateMarkerOffset(error, mMarkerAttributes); // enables own category under Java Problem Type // setting for Problems view (RFE 1530366) mMarkerAttributes.put("categoryId", Integer.valueOf(999)); //$NON-NLS-1$ // create a marker for the actual resource IMarker marker = mResource.createMarker(CheckstyleMarker.MARKER_ID); marker.setAttributes(mMarkerAttributes); mMarkerCount++; // clear the marker attributes to reuse the map for the // next error mMarkerAttributes.clear(); } } } catch (CoreException e) { CheckstyleLog.log(e); } } @Override public void addException(AuditEvent event, Throwable throwable) { CheckstyleLog.log(throwable); } @Override public void fileFinished(AuditEvent event) { // update monitor according to the monitor interval if (mMonitorCounter == MONITOR_INTERVAL) { mMonitor.worked(MONITOR_INTERVAL); mMonitorCounter = 0; } disconnectFileBuffer(mResource); mDocument = null; } @Override public void auditFinished(AuditEvent event) { cleanup(); } @Override public void auditStarted(AuditEvent event) { } public void cleanup() { mDocument = null; // disconnect any leftover buffer paths, in case of an unexpected abortion for (IPath p : mConnectedFileBufferPaths) { disconnectFileBuffer(p); } } /** * Calculates the offset information for the editor annotations. * * @param error * the audit error * @param markerAttributes * the marker attributes */ private void calculateMarkerOffset(AuditEvent error, Map<String, Object> markerAttributes) { // lazy create the document for the current file if (mDocument == null) { mDocument = connectFileBuffer(mResource); } // Provide offset information for the marker to make // annotated source code possible if (mDocument != null) { try { int line = error.getLine(); IRegion lineInformation = mDocument.getLineInformation(line == 0 ? 0 : line - 1); int lineOffset = lineInformation.getOffset(); int lineLength = lineInformation.getLength(); // annotate from the error column until the end of // the line int offset = error.getLocalizedMessage().getColumnCharIndex(); markerAttributes.put(IMarker.CHAR_START, Integer.valueOf(lineOffset + offset)); markerAttributes.put(IMarker.CHAR_END, Integer.valueOf(lineOffset + lineLength)); } catch (BadLocationException e) { // seems to happen quite often so its no use to log since we // can't do anything about it // CheckstyleLog.log(e); } } } private IDocument connectFileBuffer(IResource resource) { if (!(resource instanceof IFile)) { return null; } IDocument document = null; try { IPath path = resource.getFullPath(); mFileBufferManager.connect(path, new NullProgressMonitor()); mConnectedFileBufferPaths.add(path); document = mFileBufferManager.getTextFileBuffer(path).getDocument(); } catch (CoreException e) { CheckstyleLog.log(e); } return document; } private void disconnectFileBuffer(IResource resource) { if (!(resource instanceof IFile)) { return; } IPath path = mResource.getFullPath(); disconnectFileBuffer(path); } private void disconnectFileBuffer(IPath path) { try { if (mConnectedFileBufferPaths.contains(path)) { mFileBufferManager.disconnect(path, new NullProgressMonitor()); mConnectedFileBufferPaths.remove(path); } } catch (CoreException e) { CheckstyleLog.log(e); } } private int getSeverityValue(SeverityLevel severity) { int result = IMarker.SEVERITY_WARNING; if (severity.equals(SeverityLevel.INFO)) { result = IMarker.SEVERITY_INFO; } else if (severity.equals(SeverityLevel.WARNING)) { result = IMarker.SEVERITY_WARNING; } else if (severity.equals(SeverityLevel.ERROR)) { result = IMarker.SEVERITY_ERROR; } return result; } private String getMessage(AuditEvent error) { String moduleId = error.getModuleId(); if (moduleId == null) { RuleMetadata metaData = MetadataFactory.getRuleMetadata(error.getSourceName()); if (metaData != null) { moduleId = metaData.getInternalName(); } } final String message = error.getMessage(); StringBuffer prefix = new StringBuffer(); if (mAddRuleName) { prefix.append(getRuleName(error)); } if (mAddModuleId && error.getModuleId() != null) { if (prefix.length() > 0) { prefix.append(" - "); //$NON-NLS-1$ } prefix.append(error.getModuleId()); } StringBuffer buf = new StringBuffer(); if (prefix.length() > 0) { buf.append(prefix).append(": "); //$NON-NLS-1$ } buf.append(message); return buf.toString(); } private String getRuleName(AuditEvent error) { RuleMetadata metaData = MetadataFactory.getRuleMetadata(error.getSourceName()); if (metaData == null) { return Messages.Auditor_txtUnknownModule; } return metaData.getRuleName(); } } }