// =====================================================================
//
// Copyright (C) 2012 - 2016, Philip Graf
//
// 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
//
// =====================================================================
package ch.acanda.eclipse.pmd.builder;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.xml.sax.SAXParseException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import ch.acanda.eclipse.pmd.PMDPlugin;
import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.PMDException;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleSets;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.SourceCodeProcessor;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.ast.ParseException;
/**
* Analyzes files for coding problems, bugs and inefficient code, i.e. runs PMD.
*
* @author Philip Graf
*/
public final class Analyzer {
private static final ImmutableMap<String, Language> LANGUAGES;
static {
final Map<String, Language> languages = new HashMap<>();
for (final Language language : LanguageRegistry.getLanguages()) {
for (final String extension : language.getExtensions()) {
languages.put(extension, language);
}
}
LANGUAGES = ImmutableMap.copyOf(languages);
}
/**
* Analyzes a single file.
*
* @param file The file to analyze.
* @param ruleSets The rule sets against the file will be analyzed.
* @param violationProcessor The processor that processes the violated rules.
*/
public void analyze(final IFile file, final RuleSets ruleSets, final ViolationProcessor violationProcessor) {
final Iterable<RuleViolation> violations = runPMD(file, ruleSets);
annotateFile(file, violationProcessor, violations);
}
private Iterable<RuleViolation> runPMD(final IFile file, final RuleSets ruleSets) {
try {
if (isValidFile(file, ruleSets)) {
final Language language = LANGUAGES.get(file.getFileExtension().toLowerCase());
if (isValidLanguage(language)) {
final PMDConfiguration configuration = new PMDConfiguration();
final InputStreamReader reader = new InputStreamReader(file.getContents(), file.getCharset());
final RuleContext context = PMD.newRuleContext(file.getName(), file.getRawLocation().toFile());
context.setLanguageVersion(language.getDefaultVersion());
context.setIgnoreExceptions(false);
new SourceCodeProcessor(configuration).processSourceCode(reader, ruleSets, context);
return ImmutableList.copyOf(context.getReport().iterator());
}
}
} catch (CoreException | IOException e) {
PMDPlugin.getDefault().error("Could not run PMD on file " + file.getRawLocation(), e);
} catch (final PMDException e) {
if (isIncorrectSyntaxCause(e)) {
PMDPlugin.getDefault().info("Could not run PMD because of incorrect syntax of file " + file.getRawLocation(), e);
} else {
PMDPlugin.getDefault().warn("Could not run PMD on file " + file.getRawLocation(), e);
}
}
return ImmutableList.<RuleViolation>of();
}
private void annotateFile(final IFile file, final ViolationProcessor violationProcessor, final Iterable<RuleViolation> violations) {
try {
violationProcessor.annotate(file, violations);
} catch (CoreException | IOException e) {
PMDPlugin.getDefault().error("Could not annotate the file " + file.getRawLocation(), e);
}
}
private boolean isValidFile(final IFile file, final RuleSets ruleSets) {
// derived (i.e. generated or compiled) files are not analyzed
return !file.isDerived(IResource.CHECK_ANCESTORS)
// the file must exist
&& file.isAccessible()
// the file must have an extension so we can determine the language
&& file.getFileExtension() != null
// the file must not be excluded in the pmd configuration
&& ruleSets.applies(file.getRawLocation().toFile());
}
private boolean isValidLanguage(final Language language) {
return language != null
&& language.getDefaultVersion() != null
&& language.getDefaultVersion().getLanguageVersionHandler() != null;
}
private boolean isIncorrectSyntaxCause(final PMDException e) {
final Throwable cause = e.getCause();
// syntax of a Java or JSP file is incorrect
return cause instanceof ParseException
// syntax of an XML file is incorrect
|| cause instanceof SAXParseException;
}
}