DynamicLoader.java

package autumn.lang.compiler;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.mackenziehigh.autumn.resources.Finished;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;

/**
 * An instance of this class is a custom class-loader that can load a compiled Autumn program.
 *
 * @author Mackenzie High
 */
@Finished("2014/08/18")
public final class DynamicLoader
        extends URLClassLoader
{
    /**
     * This map maps a name to its related class-object.
     */
    private final Map<String, Class> defined = Maps.newHashMap();

    /**
     * This is the program that this loader loads.
     */
    private final CompiledProgram program;

    /**
     * Sole Constructor.
     *
     * @param parent is the parent of the new class-loader.
     * @param program is the program that is being dynamically loaded.
     */
    public DynamicLoader(final ClassLoader parent,
                         final CompiledProgram program)
    {
        super(program.libraries().toArray(new URL[0]), parent);

        this.program = program;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Class findClass(final String name)
            throws ClassNotFoundException
    {
        Preconditions.checkNotNull(name);

        if (defined.containsKey(name))
        {
            return defined.get(name);
        }

        for (ClassFile clazz : program.classes())
        {
            if (clazz.name().equals(name))
            {
                final byte[] bytecode = clazz.bytecode();

                final Class klass = defineClass(clazz.name(), bytecode, 0, bytecode.length);

                return klass;
            }
        }

        return super.findClass(name);
    }

    /**
     * This method invokes the program's entry-point thereby causing the program to execute.
     *
     * <p>
     * This method does fails quietly, if the program's entry-point was not specified.
     * </p>
     *
     * @param args are the arguments to pass to the main method.
     * @throws ClassNotFoundException if the main class does not exist.
     * @throws NoSuchMethodException if the main method does not exist.
     * @throws InvocationTargetException if main method throws an exception.
     * @throws IllegalAccessException if the main method cannot be accessed reflectively.
     */
    public void invokeMain(final String[] args)
            throws ClassNotFoundException,
                   NoSuchMethodException,
                   InvocationTargetException,
                   IllegalAccessException
    {
        Preconditions.checkNotNull(args);

        /**
         * Get the name of the module that is the entry-point of the program.
         */
        final String name = program.mainClass();

        /**
         * Fail quietly, if no entry point was specified.
         */
        if (name == null)
        {
            return;
        }

        /**
         * Reflectively find the main class.
         */
        final Class main_class = Class.forName(name, false, this);

        /**
         * Reflectively find the main function.
         */
        final Method main = main_class.getMethod("main", String[].class);

        /**
         * Invoke the main function.
         */
        main.invoke(null, (Object) args);
    }
}