/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.eclipse.compiler;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Tests Eclipse compiler integration.
 * @author Guillaume Sauthier
 */
public class EclipseCompilerTestCase {

    /**
     * List of source files to be compiled.
     */
    private List<String> list;

    /**
     * Compiler to use.
     */
    private JOnASCompiler compiler;

    /**
     * Build directory.
     */
    private File out;

    /**
     * Sources directory.
     */
    private File in;

    /**
     * Invoked before each test method execution.
     */
    @BeforeMethod
    private void prepare() {

        // Where are the sources ?
        String src = System.getProperty("my.src.dir");
        in = new File(src);

        // Where should I put the generated class files ?
        String output = System.getProperty("my.output.dir");
        out = new File(output);

        // Create the compilation context
        CompilationContext context = new CompilationContext();
        context.setContextualClassLoader(this.getClass().getClassLoader());
        context.setOutputDirectory(out);
        context.setSourceDirectory(in);

        // Assure that output dir is created.
        if (!out.exists()) {
            out.mkdirs();
            out.deleteOnExit();
        }

        // Assign the empty list of sources to be compiled
        list = new ArrayList<String>();
        context.setSources(list);

        // create the compiler
        compiler = new JOnASCompiler(context);
    }

    /**
     * Test a simple compilation.
     * Simple has no dependencies other than String.
     * @throws Exception error
     */
    @Test
    public void testSimpleCompilation() throws Exception {

        Assert.assertTrue(new File(in,
                                   convertToSystem("test/simple/Simple.java")).isFile(),
                          "No source to compile.");
        list.add(convertToSystem("test/simple/Simple.java"));

        List<CompilerError> errors = compiler.compile();

        Assert.assertTrue(errors.isEmpty(), "There are compilation errors.");
        Assert.assertTrue(new File(out,
                                   convertToSystem("test/simple/Simple.class")).isFile(),
                          "Class 'test.simple.Simple' not compiled.");

    }

    /**
     * Try the compiler class.
     * @throws Exception if compilation was erroneus
     */
    @Test(dependsOnMethods={"testSimpleCompilation"})
    public void testSimpleExecution() throws Exception {

        ClassLoader cl = new URLClassLoader(new URL[] {out.toURL()});

        Object ret = invokedCompiledHelloMethod(cl, "test.simple.Simple");
        Assert.assertEquals((String) ret, "Hello Guillaume", "Cannot execute method");

        cl = null;
        System.gc();
    }

    /**
     * Test a compilation located in a package starting with upper case letter.
     * Simple has no dependencies other than String.
     * @throws Exception error
     */
    @Test
    public void testUpperCaseCompilation() throws Exception {

        Assert.assertTrue(new File(in,
                                   convertToSystem("test/UpperCase/UpperCasePackage.java")).isFile(),
                          "No source to compile.");
        list.add(convertToSystem("test/UpperCase/UpperCasePackage.java"));

        List<CompilerError> errors = compiler.compile();

        Assert.assertTrue(errors.isEmpty(), "There are compilation errors.");
        Assert.assertTrue(new File(out,
                                   convertToSystem("test/UpperCase/UpperCasePackage.class")).isFile(),
                          "Class 'test.UpperCase.UpperCasePackage' not compiled.");

    }

    /**
     * Try the compiled class.
     * @throws Exception if execution goes wrong.
     */
    @Test(dependsOnMethods={"testUpperCaseCompilation"})
    public void testUpperCaseExecution() throws Exception {

        ClassLoader cl = new URLClassLoader(new URL[] {out.toURL()});

        Object ret = invokedCompiledHelloMethod(cl, "test.UpperCase.UpperCasePackage");
        Assert.assertEquals((String) ret, "Hello Guillaume", "Cannot execute method");

        cl = null;
        System.gc();
    }

    /**
     * Try to compile a java source file with 2 classes inside.
     * @throws Exception if compilation fails.
     */
    @Test
    public void testMultipleCompilation() throws Exception {

        Assert.assertTrue(new File(in,
                                   convertToSystem("test/multiple/Multiple.java")).isFile(),
                          "No source to compile.");
        list.add(convertToSystem("test/multiple/Multiple.java"));

        List<CompilerError> errors = compiler.compile();

        Assert.assertTrue(errors.isEmpty(), "There are compilation errors.");
        Assert.assertTrue(new File(out,
                                   convertToSystem("test/multiple/Multiple.class")).isFile(),
                          "Class 'test.multiple.Multiple' not compiled.");
        Assert.assertTrue(new File(out,
                                   convertToSystem("test/multiple/Local.class")).isFile(),
                          "Class 'test.multiple.Local' not compiled.");

    }

    /**
     * Try to execute compiled class.
     * @throws Exception error
     */
    @Test(dependsOnMethods={"testMultipleCompilation"})
    public void testMultipleExecution() throws Exception {

        ClassLoader cl = new URLClassLoader(new URL[] {out.toURL()});

        Object ret = invokedCompiledHelloMethod(cl, "test.multiple.Multiple");
        Assert.assertEquals((String) ret, "Hello Guillaume", "Cannot execute method");

        cl = null;
        System.gc();
    }

    /**
     * Try to compile a java source file with multiple classes inside +
     * dependencies outside of the compilation unit.
     * @throws Exception error
     */
    @Test
    public void testInheritanceCompilation() throws Exception {

        Assert.assertTrue(new File(in,
                                   convertToSystem("test/inheritance/Inheritance.java")).isFile(),
                          "No source to compile.");
        list.add(convertToSystem("test/inheritance/Inheritance.java"));

        List<CompilerError> errors = compiler.compile();

        Assert.assertTrue(errors.isEmpty(), "There are compilation errors.");
        Assert.assertTrue(new File(out,
                                   convertToSystem("test/inheritance/Inheritance.class")).isFile(),
                          "Class 'test.inheritance.Inheritance' not compiled.");
        Assert.assertTrue(new File(out,
                                   convertToSystem("test/inheritance/Local.class")).isFile(),
                          "Class 'test.inheritance.Local' not compiled.");

    }

    /**
     * Try to execute compiled class.
     * @throws Exception error
     */
    @Test(dependsOnMethods={"testInheritanceCompilation"})
    public void testInheritanceExecution() throws Exception {

        // use a parent classloader as classes dependencies are in it.
        ClassLoader cl = new URLClassLoader(new URL[] {out.toURL()}, this.getClass().getClassLoader());

        Object ret = invokedCompiledHelloMethod(cl, "test.inheritance.Inheritance");
        Assert.assertEquals((String) ret, "Hello Guillaume", "Cannot execute method");

        cl = null;
        System.gc();
    }

    /**
     * @param path location to be converted
     * @return a system compatible File.
     */
    private static String convertToSystem(final String path) {
        if (File.separatorChar == '/') {
            // return as is
            return path;
        }

        // convert to OS specific path
        return path.replace('/', File.separatorChar);
    }

    /**
     * Invoke the hello(String) method on a newly created instance
     * of the freshly compiled class.
     * @param cl loader to use for Class laoding
     * @param classname class to load
     * @return the String result
     * @throws Exception if something failed.
     */
    private static Object invokedCompiledHelloMethod(final ClassLoader cl,
                                                     final String classname)
            throws Exception {
        Class<?> clazz = cl.loadClass(classname);
        Method m = clazz.getDeclaredMethod("hello", new Class[] {String.class});
        Object o = clazz.newInstance();
        Object ret = m.invoke(o, new Object[] {"Guillaume"});
        return ret;
    }

}
