bluejelly.runtime.ModuleLoader.java Source code

Java tutorial

Introduction

Here is the source code for bluejelly.runtime.ModuleLoader.java

Source

/*
 * The Bluejelly project, Copyright 2012.
 *
 * This source code is distributed under the terms of 
 * the BSD license, see the LICENSE file for details.
 */

package bluejelly.runtime;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_6;

import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Set;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import bluejelly.runtime.ModuleReader.CodeInfo;
import bluejelly.runtime.ModuleReader.ModuleInfo;
import bluejelly.runtime.nodes.Caf;
import bluejelly.runtime.nodes.Code;

/**
 * Loads a single module.
 * 
 * <p>
 * <em>NB:</em> This class is <em>NOT</em> thread-safe. It's 
 * responsibility of the users of this class (the {@link Runtime}) 
 * to properly ensure thread-safety.
 * 
 * @author ppedemon
 */
public class ModuleLoader {

    /**
     * Ad-hoc, trivial class loader.
     */
    private static class CodeLoader extends ClassLoader {
        public CodeLoader(ClassLoader parent) {
            super(parent);
        }

        public Class<?> defineClass(String name, byte[] b) {
            String normalizedName = name.replace('/', '.');
            return this.defineClass(normalizedName, b, 0, b.length);
        }
    }

    // We use this to generate a unique prefix for name of generated classes
    private static int count = 0;

    // Package where generated code will live
    private static final String CODE_PKG = "org/bluejelly/code";

    // Name prefix for all generated commands
    private static final String CODE_PREFIX = "C";

    private final Runtime runtime;
    private final CodeLoader loader;
    private final Set<String> loaded;

    /**
     * Default constructor.
     */
    public ModuleLoader(Runtime runtime) {
        this.runtime = runtime;
        this.loader = new CodeLoader(this.getClass().getClassLoader());
        this.loaded = new HashSet<String>();
    }

    /**
     * Was the given module loaded?
     * @param moduleName    name of module to look for
     * @return              whether module was or not loaded
     */
    public boolean isLoaded(String moduleName) {
        return loaded.contains(moduleName);
    }

    /**
     * Load the given module into the runtime. 
     * <b>Precondition:</b> modules wasn't loaded before
     * 
     * @param moduleName    name of module to load
     * @throws JellyException    if loading fails
     */
    @SuppressWarnings("unchecked")
    public void load(String moduleName) throws JellyException {

        // Nothing to do if we already loaded this
        if (this.loaded.contains(moduleName))
            return;

        try {
            // Instantiate module
            Class<Module> mcls = (Class<Module>) Class.forName(moduleName);
            Constructor<Module> cons = mcls.getConstructor();
            Module module = cons.newInstance();

            // Read code and dictionaries from a module
            ModuleReader c = new ModuleReader();
            ModuleInfo mInfo = c.read(module);

            // Register code in runtime
            for (CodeInfo ci : mInfo.getFunctions()) {
                if (ci.getArity() == 0) {
                    Class<Caf> clazz = this.generateCaf(module, ci);
                    this.registerCaf(clazz, module, ci);
                } else {
                    Class<Code> clazz = this.generateCode(module, ci);
                    this.registerCode(clazz, module, ci);
                }
            }

            this.loaded.add(moduleName);
        } catch (Exception e) {
            throw new JellyException(e);
        }
    }

    /**
     * Generate a {@link Caf} subclass for the given method information.
     * 
     * @param module    module declaring the method to process
     * @param ci        code info for the method to process
     * 
     * @return
     *   a {@link Caf} subclass whose <code>fastEnter</code> will call
     *   module.ci.getMethod() with an {@link ExecutionContext} as argument.
     */
    @SuppressWarnings("unchecked")
    private Class<Caf> generateCaf(Module module, CodeInfo ci) {
        String nodeName = this.genNodeName(ci);
        String cafName = Type.getInternalName(Caf.class);
        String moduleName = Type.getInternalName(module.getClass());
        String methodName = ci.getMethod().getName();

        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL, nodeName, null, cafName, null);

        {
            String consDesc = Type.getMethodDescriptor(Type.VOID_TYPE,
                    new Type[] { Type.getType(Module.class), Type.getType(String.class) });

            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", consDesc, null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitMethodInsn(INVOKESPECIAL, cafName, "<init>", consDesc);
            mv.visitInsn(RETURN);
            mv.visitMaxs(3, 3);
            mv.visitEnd();
        }

        this.generateFastEnter(cw, cafName, moduleName, methodName);

        cw.visitEnd();

        byte[] b = cw.toByteArray();
        Class<?> clazz = this.loader.defineClass(nodeName, b);
        return (Class<Caf>) clazz;
    }

    /**
     * Generate a {@link Code} subclass for the given method information.
     * 
     * @param module    module declaring the method to process
     * @param ci        code info for the method to process
     * 
     * @return          
     *   a {@link Code} subclass whose <code>fastEnter</code> will call
     *   module.ci.getMethod() with an {@link ExecutionContext} as argument.
     */
    @SuppressWarnings("unchecked")
    private Class<Code> generateCode(Module module, CodeInfo ci) {
        String nodeName = this.genNodeName(ci);
        String codeName = Type.getInternalName(Code.class);
        String moduleName = Type.getInternalName(module.getClass());
        String methodName = ci.getMethod().getName();

        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL, nodeName, null, codeName, null);

        {
            String consDesc = Type.getMethodDescriptor(Type.VOID_TYPE,
                    new Type[] { Type.getType(int.class), Type.getType(Module.class), Type.getType(String.class) });

            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", consDesc, null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ILOAD, 1);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitVarInsn(ALOAD, 3);
            mv.visitMethodInsn(INVOKESPECIAL, codeName, "<init>", consDesc);
            mv.visitInsn(RETURN);
            mv.visitMaxs(4, 4);
            mv.visitEnd();
        }

        this.generateFastEnter(cw, codeName, moduleName, methodName);

        cw.visitEnd();

        byte[] b = cw.toByteArray();
        Class<?> clazz = this.loader.defineClass(nodeName, b);
        return (Class<Code>) clazz;
    }

    /**
     * Override <code>fastEnter</code> method in class being visited
     * by <code>cw</code>, subclass of <code>superName</code>. Method
     * will call <code>methodName</code>. declared in class 
     * <code>moduleName</code>. So , assuming class generated by
     * <code>cw</code> is named <code>X</code>, we will have:
     * 
     * <p>
     * <code>
     * class X extends superName {
     *     public void fastEnter(ExecutionContext ctx) {
     *         ((moduleName)this.module).methodName(ctx);
     *     }
     * }
     * </code>
     * 
     * @param cw            class writer generating class code
     * @param superName     internal name of superclass: Code or Caf
     * @param moduleName    internal name of module class
     * @param methodName    name of method to invoke in module moduleName
     */
    private void generateFastEnter(ClassWriter cw, String superName, String moduleName, String methodName) {

        String fieldDesc = Type.getDescriptor(Module.class);
        String methodDesc = "(" + Type.getDescriptor(ExecutionContext.class) + ")V";

        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "fastEnter", methodDesc, null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, superName, "module", fieldDesc);
        mv.visitTypeInsn(CHECKCAST, moduleName);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKEVIRTUAL, moduleName, methodName, methodDesc);
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

    /**
     * Register the given {@link Caf} node.
     * 
     * @param clazz    class for caf node to register
     * @param m        module where caf comes from
     * @param ci       code info for node to register
     * 
     * @throws JellyException  if code class can't be instantiated
     */
    private void registerCaf(Class<Caf> clazz, Module m, CodeInfo ci) throws JellyException {

        try {
            String functionName = ci.getMethod().getName();
            Constructor<Caf> c = clazz.getConstructor(Module.class, String.class);
            Caf caf = c.newInstance(m, functionName);
            // If this caf is a tycon matcher continuation, mark it as such
            if (ci.isMatcher())
                caf.markAsMatcher();
            this.runtime.register(IdUtils.qualify(m, functionName), caf);
        } catch (Exception e) {
            throw new JellyException(e);
        }
    }

    /**
     * Register the given {@link Code} node.
     * 
     * @param clazz    class for code node to register
     * @param m        module where code comes from
     * @param ci       code info for node to register
     * 
     * @throws JellyException  if code class can't be instantiated
     */
    private void registerCode(Class<Code> clazz, Module m, CodeInfo ci) throws JellyException {

        try {
            String functionName = ci.getMethod().getName();
            Constructor<Code> c = clazz.getConstructor(int.class, Module.class, String.class);
            Code code = c.newInstance(ci.getArity(), m, functionName);
            this.runtime.register(IdUtils.qualify(m, functionName), code);
        } catch (Exception e) {
            throw new JellyException(e);
        }
    }

    /**
     * Generate a unique name from the given code info object.
     * 
     * @param ci   input code info
     * @return     unique name
     */
    private String genNodeName(CodeInfo ci) {
        return new StringBuilder().append(CODE_PKG).append('/').append(CODE_PREFIX).append('$').append(count++)
                .toString();
    }
}