All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.net.causal.maven.plugins.boxdb.JavaRunner Maven / Gradle / Ivy

There is a newer version: 3.3
Show newest version
package au.net.causal.maven.plugins.boxdb;

import au.net.causal.maven.plugins.boxdb.db.RunnerDependency;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.DependencyResolutionException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Runs Java classes with static main methods or other methods against objects.  Uses Maven to look up and
 * resolve dependencies.
 */
public class JavaRunner
{
    private final List jarFiles;
    private final String mainClassName;
    private final ClassLoaderCache classLoaderCache;

    private Thread runnerThread;

    /**
     * Creates a runner.
     *
     * @param jarFiles list of JAR files that will form the classpath for looking up the main class.
     * @param mainClassName binary name of the class to load.
     * @param classLoaderCache classloader cache used to avoid repeatedly creating classloaders from the same
     *                         dependencies.
     *
     * @throws NullPointerException if any parameter is null.
     */
    public JavaRunner(List jarFiles, String mainClassName, ClassLoaderCache classLoaderCache)
    {
        Objects.requireNonNull(jarFiles, "jarFiles == null");
        Objects.requireNonNull(mainClassName, "mainClassName == null");
        Objects.requireNonNull(classLoaderCache, "classLoaderCache == null");
        this.jarFiles = new ArrayList<>(jarFiles);
        this.mainClassName = mainClassName;
        this.classLoaderCache = classLoaderCache;
    }

    /**
     * Creates a runner from dependencies.
     *
     * @param mainClassName the binary name of the class.
     * @param dependencies dependencies that will form the classpath used to load the main class.
     * @param repositorySystem used for looking up dependencies with Maven.
     * @param repositorySystemSession used for looking up dependencies with Maven.
     * @param remoteRepositories used for looking up dependencies with Maven.
     * @param classLoaderCache classloader cache used to avoid repeatedly creating classloaders from the same
     *                         dependencies.
     *
     * @return the created runner.
     *
     * @throws DependencyResolutionException if required dependencies could not be found by Maven.
     */
    public static JavaRunner createFromDependencies(String mainClassName,
                                                    List dependencies,
                                                    RepositorySystem repositorySystem,
                                                    RepositorySystemSession repositorySystemSession,
                                                    List remoteRepositories, 
                                                    ClassLoaderCache classLoaderCache)
    throws DependencyResolutionException
    {
        return new JavaRunner(DependencyUtils.resolveDependencies(dependencies, repositorySystem, repositorySystemSession, remoteRepositories),
                        mainClassName, classLoaderCache);
    }

    /**
     * Creates the class object using reflection using the Java runner's specifications.
     *
     * @return the created class.
     *
     * @throws IOException if an I/O error occurs.
     * @throws ClassNotFoundException if the class could not be found.
     */
    public Class makeClass()
    throws IOException, ClassNotFoundException
    {
        List urls = new ArrayList<>(jarFiles.size());
        for (Path jarFile : jarFiles)
        {
            urls.add(jarFile.toUri().toURL());
        }
        URL[] urlArray = urls.toArray(new URL[urls.size()]);
        URLClassLoader loader = classLoaderCache.create(urlArray);

        return Class.forName(mainClassName, true, loader);
    }

    /**
     * Creates the main class then looks up the main method using reflection.
     *
     * @return the main method of the main class.
     *
     * @throws IOException if an I/O error occurs.
     * @throws ClassNotFoundException if the main class could not be found.
     * @throws NoSuchMethodException if the main method could not be found on the main class.
     */
    private Method createMainMethod()
    throws IOException, ClassNotFoundException, NoSuchMethodException
    {
        Class mainClass = makeClass();
        return mainClass.getMethod("main", String[].class);
    }

    /**
     * Executes the main method synchronously, waiting for completion.
     *
     * @param args arguments passed to the main method.
     *
     * @throws IOException if an I/O error occurs.
     * @throws ClassNotFoundException if a class could not be found using reflection.
     * @throws NoSuchMethodException if the main method was not found on the class.
     * @throws InvocationTargetException if an error occurs running the main method.
     */
    public void execute(String... args)
    throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException
    {
        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
        try
        {
            Method mainMethod = createMainMethod();
            Thread.currentThread().setContextClassLoader(mainMethod.getDeclaringClass().getClassLoader());
            runMain(mainMethod, args);
        }
        finally
        {
            Thread.currentThread().setContextClassLoader(oldLoader);
        }
    }

    /**
     * Starts executing the main method, does not wait for completion.
     *
     * @param args arguments to pass to the main method.
     *
     * @throws IOException if an I/O error occurs.
     * @throws ClassNotFoundException if a class could not be found using reflection.
     * @throws NoSuchMethodException if the main method was not found on the class.
     */
    public void executeAsync(String... args)
    throws IOException, ClassNotFoundException, NoSuchMethodException
    {
        Method mainMethod = createMainMethod();

        runnerThread = new Thread(() -> runMainSafely(mainMethod, args));
        runnerThread.setContextClassLoader(mainMethod.getDeclaringClass().getClassLoader());
        runnerThread.setName("JavaRunner-" + mainMethod.getDeclaringClass().getSimpleName());
        //runnerThread.setUncaughtExceptionHandler();

        runnerThread.start();
    }

    /**
     * Runs a custom method from the main class asynchronously on an instance, without waiting for completion.
     *
     * @param method the method to run.
     * @param target the target instance object whose method to run.
     * @param args arguments to the method.
     */
    public void runMethodAsync(Method method, Object target, Object... args)
    {
        runnerThread = new Thread(() -> runMethod(method, target, args));
        runnerThread.setContextClassLoader(method.getDeclaringClass().getClassLoader());
        runnerThread.setName("JavaRunner-" + method.getDeclaringClass().getSimpleName());
        //runnerThread.setUncaughtExceptionHandler();

        runnerThread.start();
    }

    /**
     * Runs a custom method from the main class synchronously, waiting for it to complete.
     *
     * @param method the method to run.
     * @param target the target instance object whose method to run.
     * @param args arguments to the method.
     *
     * @throws RuntimeException if an error occurs running the method.
     */
    public void runMethod(Method method, Object target, Object... args)
    {
        try
        {
            method.invoke(target, args);
        }
        catch (ReflectiveOperationException e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * Runs the static main method, wrapping thrown exceptions in runtime exceptions.
     *
     * @param mainMethod the main method to execute.
     * @param args arguments to pass the main method.
     *
     * @throws RuntimeException if an error occurs running the main method.
     */
    private void runMainSafely(Method mainMethod, String... args)
    {
        try
        {
            mainMethod.invoke(null, new Object[] {args});
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * Run the static main method, wrapping thrown exceptions if needed.
     *
     * @param mainMethod the main method to execute.
     * @param args arguments to pass to the main method.
     *
     * @throws InvocationTargetException if an invocation or reflective operation occurs.
     */
    private void runMain(Method mainMethod, String... args)
    throws InvocationTargetException
    {
        try
        {
            mainMethod.invoke(null, new Object[] {args});
        }
        catch (InvocationTargetException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new InvocationTargetException(e);
        }
    }

    /**
     * Wait for a previously asynchronously executed method to finish execution.
     *
     * @throws InterruptedException if interrupted while waiting.
     */
    public void waitForAsyncCompletion()
    throws InterruptedException
    {
        if (runnerThread == null)
            throw new IllegalStateException("Thread never started");

        runnerThread.join();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy