/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2007 University of Maryland
 *
 * 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 edu.umd.cs.findbugs;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import junit.framework.TestCase;
import edu.umd.cs.findbugs.config.UserPreferences;

/**
 * Abstract base class for TestCase classes that need to run in the context of a
 * FindBugs2 object doing a full execution. Ensures that things like
 * AnalysisCache, AnalysisContext, etc. are fully initialized.
 *
 * <p>
 * Is this mock objects? Or is this just a hack? Probably the latter :-)
 *
 * @author David Hovemeyer
 */
public abstract class FindBugsTestCase extends TestCase {
    /**
     * Data of an empty class in the default package called "Empty".
     */
    public static final byte[] EMPTY_CLASS_DATA = { (byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x32, (byte) 0x00, (byte) 0x0d, (byte) 0x0a, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x0a,
        (byte) 0x07, (byte) 0x00, (byte) 0x0b, (byte) 0x07, (byte) 0x00, (byte) 0x0c, (byte) 0x01, (byte) 0x00, (byte) 0x06,
        (byte) 0x3c, (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x3e, (byte) 0x01, (byte) 0x00, (byte) 0x03,
        (byte) 0x28, (byte) 0x29, (byte) 0x56, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x43, (byte) 0x6f, (byte) 0x64,
        (byte) 0x65, (byte) 0x01, (byte) 0x00, (byte) 0x0f, (byte) 0x4c, (byte) 0x69, (byte) 0x6e, (byte) 0x65, (byte) 0x4e,
        (byte) 0x75, (byte) 0x6d, (byte) 0x62, (byte) 0x65, (byte) 0x72, (byte) 0x54, (byte) 0x61, (byte) 0x62, (byte) 0x6c,
        (byte) 0x65, (byte) 0x01, (byte) 0x00, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x75, (byte) 0x72, (byte) 0x63,
        (byte) 0x65, (byte) 0x46, (byte) 0x69, (byte) 0x6c, (byte) 0x65, (byte) 0x01, (byte) 0x00, (byte) 0x0a, (byte) 0x45,
        (byte) 0x6d, (byte) 0x70, (byte) 0x74, (byte) 0x79, (byte) 0x2e, (byte) 0x6a, (byte) 0x61, (byte) 0x76, (byte) 0x61,
        (byte) 0x0c, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x00, (byte) 0x05, (byte) 0x45,
        (byte) 0x6d, (byte) 0x70, (byte) 0x74, (byte) 0x79, (byte) 0x01, (byte) 0x00, (byte) 0x10, (byte) 0x6a, (byte) 0x61,
        (byte) 0x76, (byte) 0x61, (byte) 0x2f, (byte) 0x6c, (byte) 0x61, (byte) 0x6e, (byte) 0x67, (byte) 0x2f, (byte) 0x4f,
        (byte) 0x62, (byte) 0x6a, (byte) 0x65, (byte) 0x63, (byte) 0x74, (byte) 0x00, (byte) 0x21, (byte) 0x00, (byte) 0x02,
        (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00,
        (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06,
        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x1d, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00,
        (byte) 0x00, (byte) 0x00, (byte) 0x05, (byte) 0x2a, (byte) 0xb7, (byte) 0x00, (byte) 0x01, (byte) 0xb1, (byte) 0x00,
        (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x06,
        (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00,
        (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x09, };

    private static final class TestRunnerThread extends Thread {
        private final RunnableWithExceptions runnable;

        private JUnitDetectorAdapter detectorAdapter;

        private TestRunnerThread(RunnableWithExceptions runnable) {
            this.runnable = runnable;
        }

        /**
         * @return Returns the detectorAdapter.
         */
        public JUnitDetectorAdapter getDetectorAdapter() {
            return detectorAdapter;
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            try {
                runTest(runnable);
            } catch (Exception e) {
                // Hmm...
                System.err.println("Exception running test:");
                e.printStackTrace();
            }
        }

        private void runTest(RunnableWithExceptions runnable) throws IOException, InterruptedException {
            // Create temporary directory in filesystem
            File tmpdir = File.createTempFile("fbtest", null);
            if (!tmpdir.delete() || !tmpdir.mkdir()) {
                throw new IOException("Could not create temp dir");
            }

            File tmpfile = null;

            try {
                // Create a class file to analyze
                tmpfile = createEmptyClassFile(tmpdir);

                // Unfortunately there's quite a bit of gobbledygook required
                // to set up a FindBugs2.

                FindBugs2 engine = new FindBugs2();

                engine.setBugReporter(new PrintingBugReporter());

                // Analyze the temporary directory we just created
                Project project = new Project();
                project.addFile(tmpdir.getAbsolutePath());

                engine.setProject(project);
                PluginLoader fakeLoader = new PluginLoader(true, tmpfile.toURL());
                Plugin fakePlugin = new Plugin("edu.umd.cs.findbugs.fakeplugin", null, null, fakeLoader, true, false);

                DetectorFactoryCollection dfc = new DetectorFactoryCollection(fakePlugin);
                DetectorFactoryCollection.resetInstance(dfc);

                DetectorFactory detectorFactory = new DetectorFactory(fakePlugin, JUnitDetectorAdapter.class.getName(),
                        JUnitDetectorAdapter.class, true, "fast", "", "");
                fakePlugin.addDetectorFactory(detectorFactory);
                dfc.registerDetector(detectorFactory);
                if (!dfc.factoryIterator().hasNext() || fakePlugin.getDetectorFactories().isEmpty()) {
                    throw new IllegalStateException();
                }

                engine.setDetectorFactoryCollection(dfc);

                engine.setUserPreferences(UserPreferences.createDefaultUserPreferences());

                JUnitDetectorAdapter.setRunnable(runnable);

                engine.execute();

                // Get a handle to the JUnitDetectorAdapter, since it is the
                // object that knows whether or not the test code actually
                // passed
                // or failed.
                detectorAdapter = JUnitDetectorAdapter.instance();
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                if (tmpfile != null) {
                    deleteAndLog(tmpfile);
                }
                deleteAndLog(tmpdir);
                DetectorFactoryCollection.resetInstance(null);

            }
        }

        void deleteAndLog(File f) {
            if (!f.delete()) {
                System.err.println("Could not delete " + f);
            }
        }

        /**
         * @param tmpdir
         * @throws IOException
         */
        private File createEmptyClassFile(File tmpdir) throws IOException {
            File outFile = new File(tmpdir, "Empty.class");
            OutputStream out = new FileOutputStream(outFile);
            try {
                out.write(EMPTY_CLASS_DATA);
            } finally {
                out.close();
            }
            return outFile;
        }
    }

    /**
     * Execute some JUnit test code inside a Detector2 class running inside a
     * FindBugs2 analysis run. In theory, any code legal in a FindBugs detector
     * should work.
     *
     * @param runnable
     *            a RunnableWithExceptions object whose run() method has some
     *            JUnit test code
     * @throws Exception
     */
    protected void executeFindBugsTest(final RunnableWithExceptions runnable) throws Exception {
        TestRunnerThread thread = new TestRunnerThread(runnable);

        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }

        if (thread.getDetectorAdapter() == null) {
            throw new IllegalStateException("Test code did not complete");
        }
        thread.getDetectorAdapter().finishTest();
    }
}
