package org.infernus.idea.checkstyle;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infernus.idea.checkstyle.checker.CheckerFactory;
import org.infernus.idea.checkstyle.checker.Problem;
import org.infernus.idea.checkstyle.checker.ScannableFile;
import org.infernus.idea.checkstyle.csapi.SeverityLevel;
import org.infernus.idea.checkstyle.exception.CheckStylePluginException;
import org.infernus.idea.checkstyle.exception.CheckStylePluginParseException;
import org.infernus.idea.checkstyle.model.ConfigurationLocation;
import org.infernus.idea.checkstyle.ui.CheckStyleInspectionPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.infernus.idea.checkstyle.CheckStyleBundle.message;
import static org.infernus.idea.checkstyle.util.Async.asyncResultOf;
import static org.infernus.idea.checkstyle.util.Notifications.showWarning;
public class CheckStyleInspection extends LocalInspectionTool {
private static final Log LOG = LogFactory.getLog(CheckStyleInspection.class);
private static final List<Problem> NO_PROBLEMS_FOUND = Collections.emptyList();
private final CheckStyleInspectionPanel configPanel = new CheckStyleInspectionPanel();
private CheckStylePlugin plugin(final Project project) {
final CheckStylePlugin checkStylePlugin = project.getComponent(CheckStylePlugin.class);
if (checkStylePlugin == null) {
throw new IllegalStateException("Couldn't get checkstyle plugin");
}
return checkStylePlugin;
}
@Nullable
public JComponent createOptionsPanel() {
return configPanel;
}
@Override
public ProblemDescriptor[] checkFile(@NotNull final PsiFile psiFile,
@NotNull final InspectionManager manager,
final boolean isOnTheFly) {
final Module module = moduleOf(psiFile);
return asProblemDescriptors(asyncResultOf(() -> inspectFile(psiFile, module, manager), NO_PROBLEMS_FOUND), manager);
}
@Nullable
private Module moduleOf(@NotNull final PsiFile psiFile) {
return ModuleUtil.findModuleForPsiElement(psiFile);
}
@Nullable
public List<Problem> inspectFile(@NotNull final PsiFile psiFile,
@Nullable final Module module,
@NotNull final InspectionManager manager) {
LOG.debug("Inspection has been invoked.");
final CheckStylePlugin plugin = plugin(manager.getProject());
ConfigurationLocation configurationLocation = null;
final List<ScannableFile> scannableFiles = new ArrayList<>();
try {
configurationLocation = plugin.getConfigurationLocation(module, null);
if (configurationLocation == null || configurationLocation.isBlacklisted()) {
return NO_PROBLEMS_FOUND;
}
scannableFiles.addAll(ScannableFile.createAndValidate(singletonList(psiFile), plugin, module));
return checkerFactory(psiFile.getProject())
.checker(module, configurationLocation)
.map(checker -> checker.scan(scannableFiles, plugin.getConfiguration().isSuppressingErrors()))
.map(results -> results.get(psiFile))
.map(this::dropIgnoredProblems)
.orElseGet(() -> NO_PROBLEMS_FOUND);
} catch (ProcessCanceledException | AssertionError e) {
LOG.debug("Process cancelled when scanning: " + psiFile.getName());
return NO_PROBLEMS_FOUND;
} catch (CheckStylePluginParseException e) {
LOG.debug("Parse exception caught when scanning: " + psiFile.getName(), e);
return NO_PROBLEMS_FOUND;
} catch (CheckStylePluginException e) {
handlePluginException(e, psiFile, plugin, configurationLocation, manager.getProject());
return NO_PROBLEMS_FOUND;
} catch (Throwable e) {
LOG.warn("The inspection could not be executed.", e);
return NO_PROBLEMS_FOUND;
} finally {
scannableFiles.forEach(ScannableFile::deleteIfRequired);
}
}
private List<Problem> dropIgnoredProblems(final List<Problem> problems) {
return problems.stream()
.filter(problem -> problem.severityLevel() != SeverityLevel.Ignore)
.collect(toList());
}
private void handlePluginException(final CheckStylePluginException e,
final @NotNull PsiFile psiFile,
final CheckStylePlugin plugin,
final ConfigurationLocation configurationLocation,
final @NotNull Project project) {
if (e.getCause() != null && e.getCause() instanceof FileNotFoundException) {
disableActiveConfiguration(plugin, project);
} else if (e.getCause() != null && e.getCause() instanceof IOException) {
showWarning(project, message("checkstyle.file-io-failed"));
blacklist(configurationLocation);
} else {
LOG.error("CheckStyle threw an exception when scanning: " + psiFile.getName(), e);
blacklist(configurationLocation);
}
}
private void disableActiveConfiguration(final CheckStylePlugin plugin, final Project project) {
plugin.getConfiguration().setActiveConfiguration(null);
showWarning(project, message("checkstyle.configuration-disabled.file-not-found"));
}
private void blacklist(final ConfigurationLocation configurationLocation) {
if (configurationLocation != null) {
configurationLocation.blacklist();
}
}
@NotNull
private ProblemDescriptor[] asProblemDescriptors(final List<Problem> results, final InspectionManager manager) {
return ofNullable(results)
.map(problems -> problems.stream()
.map(problem -> problem.toProblemDescriptor(manager))
.toArray(ProblemDescriptor[]::new))
.orElseGet(() -> ProblemDescriptor.EMPTY_ARRAY);
}
private CheckerFactory checkerFactory(final Project project) {
return ServiceManager.getService(project, CheckerFactory.class);
}
}