package edu.umd.cs.findbugs.test;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

import edu.umd.cs.findbugs.BugCollectionBugReporter;
import edu.umd.cs.findbugs.BugRanker;
import edu.umd.cs.findbugs.DetectorFactoryCollection;
import edu.umd.cs.findbugs.FindBugs2;
import edu.umd.cs.findbugs.Plugin;
import edu.umd.cs.findbugs.PluginException;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.config.UserPreferences;
import edu.umd.cs.findbugs.plugins.DuplicatePluginIdException;

/**
 * <p>
 * This class runs analysis with SpotBugs. The target class files and
 * auxClasspathEntries should be specified before you invoke {@link #run(Path...)}
 * method.
 * </p>
 *
 * @since 3.1
 */
@ParametersAreNonnullByDefault
public class AnalysisRunner {
    private final List<Path> auxClasspathEntries = new ArrayList<>();

    /**
     * SpotBugs stores relation between plugin-id and {@link Plugin} instance in a static field ({@code Plugin.allPlugins}),
     * so we need to store Plugin information in static field too, to avoid duplicated plugin loading.
     */
    @Nullable
    private static final File PLUGIN_JAR;

    static {
        File jarFile;
        try {
            jarFile = createTempJar();
            Plugin.loadCustomPlugin(jarFile, null);
        } catch (DuplicatePluginIdException ignore) {
            // loading core plugin
            jarFile = null;
        } catch (IOException | URISyntaxException | PluginException e) {
            throw new AssertionError(e);
        }

        PLUGIN_JAR = jarFile;
    }

    @Nonnull
    public AnalysisRunner addAuxClasspathEntry(Path path) {
        Objects.requireNonNull(path);
        if (!path.toFile().canRead()) {
            throw new IllegalArgumentException("Cannot read " + path.toAbsolutePath());
        }
        auxClasspathEntries.add(path);
        return this;
    }

    @Nonnull
    public BugCollectionBugReporter run(Path... files) {
        DetectorFactoryCollection.resetInstance(new DetectorFactoryCollection());

        FindBugs2 engine = new FindBugs2();
        final Project project = new Project();
        project.setProjectName(getClass().getSimpleName());
        engine.setProject(project);

        if (PLUGIN_JAR != null) try {
            String pluginId = Plugin.addCustomPlugin(PLUGIN_JAR.toURI()).getPluginId();
            project.setPluginStatusTrinary(pluginId, Boolean.TRUE);
        } catch (PluginException e) {
            throw new AssertionError("Failed to load plugin", e);
        }
        final DetectorFactoryCollection detectorFactoryCollection = DetectorFactoryCollection.instance();
        engine.setDetectorFactoryCollection(detectorFactoryCollection);

        BugCollectionBugReporter bugReporter = new BugCollectionBugReporter(project);
        bugReporter.setPriorityThreshold(Priorities.LOW_PRIORITY);
        bugReporter.setRankThreshold(BugRanker.VISIBLE_RANK_MAX);

        engine.setBugReporter(bugReporter);
        final UserPreferences preferences = UserPreferences.createDefaultUserPreferences();
        preferences.getFilterSettings().clearAllCategories();
        engine.setUserPreferences(preferences);

        for (Path file : files) {
            project.addFile(file.toAbsolutePath().toString());
        }
        for (Path auxClasspathEntry : auxClasspathEntries) {
            project.addAuxClasspathEntry(auxClasspathEntry.toAbsolutePath().toString());
        }

        try {
            engine.execute();
        } catch (final IOException | InterruptedException e) {
            throw new AssertionError("Analysis failed with exception", e);
        }
        if (!bugReporter.getQueuedErrors().isEmpty()) {
            AssertionError assertionError = new AssertionError(
                    "Analysis failed with exception. Check stderr for detail.");
            bugReporter.getQueuedErrors().stream().map(error -> error.getCause())
                    .forEach(assertionError::addSuppressed);
            throw assertionError;
        }
        return bugReporter;
    }

    /**
     * Create a jar file which contains all resource files. This is necessary to
     * let {@link Plugin#loadCustomPlugin(File, Project)} load custom plugin to
     * test.
     *
     * @return a {@link File} instance which represent generated jar file
     * @throws IOException
     * @throws URISyntaxException
     */
    private static File createTempJar() throws IOException, URISyntaxException {
        ClassLoader cl = AnalysisRunner.class.getClassLoader();

        URL resource = cl.getResource("findbugs.xml");
        URI uri = resource.toURI();

        if ("jar".equals(uri.getScheme())) {
            JarURLConnection connection = (JarURLConnection) resource.openConnection();
            URL url = connection.getJarFileURL();
            return new File(url.getFile());
        }

        Path tempJar = File.createTempFile("SpotBugsAnalysisRunner", ".jar").toPath();
        try (OutputStream output = Files.newOutputStream(tempJar, StandardOpenOption.WRITE);
                JarOutputStream jar = new JarOutputStream(output)) {
            Path resourceRoot = Paths.get(uri).getParent();

            byte[] data = new byte[4 * 1024];
            Files.walkFileTree(resourceRoot, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    String name = resourceRoot.relativize(file).toString();
                    jar.putNextEntry(new ZipEntry(name));
                    try (InputStream input = Files.newInputStream(file, StandardOpenOption.READ)) {
                        int len;
                        while ((len = input.read(data)) > 0) {
                            jar.write(data, 0, len);
                        }
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        }
        return tempJar.toFile();
    }
}

@@ -2966,23 +2966,41 @@
 != null)
+ %7B%0A           
  try %7B%0A
+    
         
@@ -3075,24 +3075,28 @@
 PluginId();%0A
+    
             
@@ -3151,32 +3151,36 @@
 .TRUE);%0A        
+    
 %7D catch (PluginE
@@ -3185,32 +3185,36 @@
 nException e) %7B%0A
+    
             thro
@@ -3255,32 +3255,46 @@
 ad plugin%22, e);%0A
+            %7D%0A
         %7D%0A      
@@ -3880,24 +3880,70 @@
 tegories();%0A
+        preferences.enableAllDetectors(true);%0A
         engi

Enable all detectors during junit tests