Package org.moyrax.javascript.instrument

Source Code of org.moyrax.javascript.instrument.ComponentClassAdapter$MethodDescriptor

package org.moyrax.javascript.instrument;

import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ALOAD;
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.IRETURN;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.moyrax.javascript.ScriptComponent;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

/**
* Processes an exportable class and generates the needed code to be recognized
* in the client application.
*
* @author Matias Mirabelli <lumen.night@gmail.com>
* @since 1.2
*/
public final class ComponentClassAdapter extends ClassAdapter {
  /**
   * This class contains the information that describes a method as it will
   * be saved in bytecode.
   *
   * @author Matias Mirabelli <lumen.night@gmail.com>
   */
  private class MethodDescriptor {
    public MethodDescriptor(final String theName,
        final String theDesc, final String theSignature,
        final String[] theExceptions) {

      this.name = theName;
      this.desc = theDesc;
      this.signature = theSignature;
      this.exceptions = theExceptions;
    }

    /**
     * The method's name.
     */
    public String name;

    /**
     * The method's descriptor.
     *
     * For more information refer to the following url: <a href="
     *  http://java.sun.com/docs/books/jvms/second_edition/html/
     *ClassFile.doc.html#7035">JVM Specification - Method Descriptors</a>
     */
    public String desc;

    /**
     * The method's signature.
     */
    public String signature;

    /**
     * List of exceptions thrown by this method.
     */
    public String[] exceptions;

    /**
     * Indicates if this method is a constructor. Default is <code>false</code>.
     */
    public boolean constructor;
  }

  /** Class which will be modified. */
  private ScriptComponent script;

  /** {@link ClassReader} to parse the script class. */
  private ClassReader reader;

  /** List of methods that will be exposed as JavaScript functions. */
  private List<String> functions;

  /** List of MethodDescriptor objects which represent the methods that will be
   * generated. */
  private ArrayList<MethodDescriptor> descriptors =
    new ArrayList<MethodDescriptor>();

  /**
   * Indicates if the script class was already transformed. Default is
   * <code>false</code>.
   */
  private boolean transformed;

  /**
   * Creates an adapter for the specified class.
   *
   * @param hostClass Class to instrument. It cannot be null.
   * @throws IOException If the class cannot be found.
   */
  public ComponentClassAdapter (final Class<?> hostClass) throws IOException {
    this(hostClass, Thread.currentThread().getContextClassLoader());
  }

  /**
   * Creates an adapter for the specified class and uses the specified
   * {@link ClassLoader} to locate the resource.
   *
   * @param hostClass Class to instrument. It cannot be null.
   * @param classLoader The class loader to search for the resource. It cannot
   *    be null.
   *
   * @throws IOException If the class cannot be found.
   */
  public ComponentClassAdapter (final Class<?> hostClass,
      final ClassLoader classLoader) throws IOException {

    this(new ClassReader(classLoader.getResourceAsStream(
        hostClass.getName().replace(".", "/") + ".class")));

    this.script = new ScriptComponent(hostClass, classLoader);
    this.functions = script.getFunctionNames();
  }

  /**
   * Creates an adapter for the specified class and uses the specified
   * {@link ClassLoader} to locate the resource.
   *
   * @param hostClass Name of the class to instrument. It cannot be null.
   * @param classLoader The class loader to search for the resource. It cannot
   *    be null.
   *
   * @throws IOException If the class cannot be found.
   */
  public ComponentClassAdapter (final String hostClass,
      final ClassLoader classLoader) throws IOException {
    this(new ClassReader(classLoader.getResourceAsStream(
        hostClass.replace(".", "/") + ".class")));

    this.script = new ScriptComponent(hostClass, classLoader);
    this.functions = script.getFunctionNames();
  }

  private ComponentClassAdapter (final ClassReader classReader) {
    super(new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES));

    this.reader = classReader;
  }

  /**
   * This method is invoked before any other visit. It allows to make changes
   * to the class definition.
   *
   * @param version VM version.
   * @param access Type access.
   * @param name Class name.
   * @param signature Class signature.
   * @param superName Name of the superclass.
   * @param interfaces List of implementing interfaces.
   */
  @Override
  public void visit(final int version, final int access, final String name,
      final String signature, final String superName,
      final String[] interfaces) {

    super.visit(version,
        ACC_PUBLIC + ACC_SUPER, /* So sorry, I need to be public. */
        name,
        signature,
        script.getImplementationClass().getName().replace(".", "/"),
        interfaces);
  }

  @Override
  public MethodVisitor visitMethod(final int access, final String name,
      final String desc, final String signature, final String[] exceptions) {

    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
        exceptions);

    if (this.functions.contains(name) ||
        (script.getConstructor() != null &&
            script.getConstructor().getName().equals(name))) {

      MethodDescriptor descriptor = new MethodDescriptor(name, desc, signature,
          exceptions);

      if (script.getConstructor() != null &&
          script.getConstructor().getName().equals(name)) {
        descriptor.constructor = true;
      }

      descriptors.add(descriptor);
    }

    /* Creates an adapter to replace the default constructor for the
       one needed by the new superclass. */
    if (name.equals("<init>")) {
      final String className = script.getImplementationClass().getName()
      .replace(".", "/");

      /* Generates the default constructor adapter.
        TODO(mmirabelli): find how to change the access of the default
        constructor, since that it seems to be private by default in a private
        class. It cause this assembly to break in private inner classes, unless
        an explicit public constructor will be defined.
       */
      mv = new MethodAdapter(mv) {
        private boolean alreadySet = false;

        @Override
        public void visitMethodInsn(final int opcode, final String klass, final String method,
            final String desc) {
          if ((opcode == INVOKESPECIAL) && !alreadySet) {
            super.visitMethodInsn(opcode, className, method, desc);

            alreadySet = true;
          } else {
            super.visitMethodInsn(opcode, klass, method, desc);
          }
        }
      };
    }

    return mv;
  }

  /**
   * @return Returns the byte array which contains the code of the transformed
   * class.
   */
  public byte[] toByteArray() {
    if (!transformed) {
      reader.accept(this, 0);

      transformed = true;
    }

    return ((ClassWriter)cv).toByteArray();
  }

  /**
   * Creates the JavaScript function-compliant method for the specified
   * Java method.
   *
   * @param descriptor The method information to create the Function.
   */
  private void generateFunction(final MethodDescriptor descriptor) {

    String methodName = descriptor.name;
    String functionPrefix = "jsFunction_";

    if (descriptor.constructor) {
      methodName = "jsConstructor";
      functionPrefix = "";
    }

    /* Creates the new method. */
    MethodVisitor mv = cv.visitMethod(
        ACC_PUBLIC, /* Please understand, I also need to be public. */
        functionPrefix + methodName,
        descriptor.desc,
        descriptor.signature,
        descriptor.exceptions);

    String className = script.getScriptableClassName().replace(".", "/");

    Type[] argumentTypes = Type.getArgumentTypes(descriptor.desc);

    /* Starts generating code. Equivalent asm code is:
     *
     * ALOAD 0
     * xLOAD n
     * ...
     * INVOKEVIRTUAL my/class/name methodName (Ljava/lang/String;)V
     * RETURN
     */
    mv.visitCode();
    /* Puts the "this" object into the stack. */
    mv.visitVarInsn(ALOAD, 0);

    /* Puts all the parameters into the stack. */
    for (int i = 0; i < argumentTypes.length; i++) {
      int opcode = argumentTypes[i].getOpcode(ILOAD);

      mv.visitVarInsn(opcode, i + 1);
    }

    /* Invokes the original method. */
    mv.visitMethodInsn(INVOKEVIRTUAL, className, descriptor.name,
        descriptor.desc);

    /* Return TYPE. */
    mv.visitInsn(Type.getReturnType(descriptor.desc).getOpcode(IRETURN));
    /* The stack frame will be calculated by the ClassWriter. */
    mv.visitMaxs(0, 0);
    /* The code generation ends. */
    mv.visitEnd();
  }

  /**
   * Instruments the getter needed by Rhino to retrieve the component's name.
   * The component name is the same that will be used in JavaScript. By default,
   * the component name is set to the class name.
   */
  private void addClassNameGetter() {
    // Adds a private field to the class to hold the component's name.
    String fieldName = "$className";

    cv.visitField(ACC_PRIVATE, fieldName, "Ljava/lang/String;", "",
        script.getClassName());

    // Creates the getter method which returns the created field.
    String methodName = "getClassName";
    MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, methodName,
        "()Ljava/lang/String;", null, null);
    mv.visitVarInsn(ALOAD, 0);
    mv.visitFieldInsn(GETFIELD,
        script.getScriptableClassName().replace(".", "/"),
        fieldName, "Ljava/lang/String;");
    mv.visitInsn(Type.getType(String.class).getOpcode(IRETURN));
    mv.visitMaxs(0, 0);
  }

  /**
   * Generates the JavaScript functions and ends generating the class.
   */
  @Override
  public void visitEnd() {
    for (MethodDescriptor descriptor : descriptors) {
      generateFunction(descriptor);
    }

    addClassNameGetter();

    cv.visitEnd();
  }
}
TOP

Related Classes of org.moyrax.javascript.instrument.ComponentClassAdapter$MethodDescriptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.