com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.java

Source

/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.shell.rewrite;

import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.javac.asmbridge.EmptyVisitor;
import com.google.gwt.dev.shell.JsValueGlue;
import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Method;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;

/**
 * This class performs any and all byte code rewriting needed to make hosted
 * mode work. Currently, it performs the following rewrites:
 * <ol>
 * <li>Rewrites all native methods into non-native thunks to call JSNI via
 * {@link com.google.gwt.dev.shell.JavaScriptHost}.</li>
 * <li>Rewrites all JSO types into an interface type (which retains the original
 * name) and an implementation type (which has a $ appended).</li>
 * <li>All JSO interface types are empty and mirror the original type hierarchy.
 * </li>
 * <li>All JSO impl types contain the guts of the original type, except that all
 * instance methods are reimplemented as statics.</li>
 * <li>Calls sites to JSO types rewritten to dispatch to impl types. Any virtual
 * calls are also made static. Static field references to JSO types reference
 * static fields in the impl class.</li>
 * <li>JavaScriptObject$ implements all the interface types and is the only
 * instantiable type.</li>
 * </ol>
 *
 * @see RewriteJsniMethods
 * @see RewriteRefsToJsoClasses
 * @see WriteJsoInterface
 * @see WriteJsoImpl
 */
public class HostedModeClassRewriter {
    /*
     * Note: this rewriter operates on a class-by-class basis and has no global
     * view on the entire system. However, its operation requires certain global
     * state information. Therefore, all such global state must be passed into the
     * constructor.
     */

    /**
     * Maps instance methods to the class in which they are declared. This must be
     * provided by the caller since it requires global program state.
     */
    public interface InstanceMethodOracle {

        /**
         * For a given instance method and declared enclosing class (which must be a
         * JSO subtype), find the class in which that method was originally
         * declared. Methods declared on Object will return "java/lang/Object".
         * Static methods will always return <code>declaredClass</code>.
         *
         * @param declaredClass a descriptor of the static type of the qualifier
         * @param signature the binary signature of the method
         * @return the descriptor of the class in which that method was declared,
         *         which will either be <code>declaredClass</code> or some
         *         superclass
         * @throws IllegalArgumentException if the method could not be found
         */
        String findOriginalDeclaringClass(String declaredClass, String signature);
    }

    /**
     * Contains data about how SingleJsoImpl methods are to be dispatched.
     */
    public interface SingleJsoImplData {
        /**
         * Returns the method declarations that should be generated for a given
         * mangled method name. {@link #getDeclarations} and
         * {@link #getImplementations} maintain a pairwise mapping.
         */
        List<Method> getDeclarations(String mangledName);

        /**
         * Returns the implementations that the method declarations above should
         * delegate to.{@link #getDeclarations} and {@link #getImplementations}
         * maintain a pairwise mapping.
         */
        List<Method> getImplementations(String mangledName);

        /**
         * Returns all of the mangled method names for SingleJsoImpl methods.
         */
        SortedSet<String> getMangledNames();

        /**
         * Returns the internal names of all interface types implemented by JSOs.
         */
        Set<String> getSingleJsoIntfTypes();
    }

    static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace('.', '/');

    static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace('.', '/');

    static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;

    static String addSyntheticThisParam(String owner, String methodDescriptor) {
        return "(L" + owner + ";" + methodDescriptor.substring(1);
    }

    private static String toDescriptor(String jsoSubtype) {
        return jsoSubtype.replace('.', '/');
    }

    /**
     * An unmodifiable set of descriptors containing the implementation form of
     * <code>JavaScriptObject</code> and all subclasses.
     */
    private final Set<String> jsoImplDescs;

    /**
     * An unmodifiable set of descriptors containing the interface form of
     * <code>JavaScriptObject</code> and all subclasses.
     */
    private final Set<String> jsoIntfDescs;

    private final SingleJsoImplData jsoData;

    /**
     * Records the superclass of every JSO for generating empty JSO interfaces.
     */
    private final Map<String, List<String>> jsoSuperDescs;

    /**
     * Maps methods to the class in which they are declared.
     */
    private InstanceMethodOracle mapper;

    /**
     * Creates a new {@link HostedModeClassRewriter} for a specified set of
     * subclasses of JavaScriptObject.
     *
     * @param jsoSubtypes a set of binary type names representing JavaScriptObject
     *          and all of its subtypes of
     * @param mapper maps methods to the class in which they are declared
     */
    public HostedModeClassRewriter(Set<String> jsoSubtypes, Map<String, List<String>> jsoSuperTypes,
            SingleJsoImplData jsoData, InstanceMethodOracle mapper) {
        Set<String> buildJsoIntfDescs = new HashSet<String>();
        Set<String> buildJsoImplDescs = new HashSet<String>();
        Map<String, List<String>> buildJsoSuperDescs = new HashMap<String, List<String>>();
        for (String jsoSubtype : jsoSubtypes) {
            String desc = toDescriptor(jsoSubtype);
            buildJsoIntfDescs.add(desc);
            buildJsoImplDescs.add(desc + "$");

            List<String> superTypes = jsoSuperTypes.get(jsoSubtype);
            assert (superTypes != null);
            assert (superTypes.size() > 0);
            for (ListIterator<String> i = superTypes.listIterator(); i.hasNext();) {
                i.set(toDescriptor(i.next()));
            }
            buildJsoSuperDescs.put(desc, Collections.unmodifiableList(superTypes));
        }

        this.jsoIntfDescs = Collections.unmodifiableSet(buildJsoIntfDescs);
        this.jsoImplDescs = Collections.unmodifiableSet(buildJsoImplDescs);
        this.jsoSuperDescs = Collections.unmodifiableMap(buildJsoSuperDescs);
        this.jsoData = jsoData;
        this.mapper = mapper;
    }

    /**
     * Returns <code>true</code> if the class is the implementation class for a
     * JSO subtype.
     */
    public boolean isJsoImpl(String className) {
        return jsoImplDescs.contains(toDescriptor(className));
    }

    /**
     * Returns <code>true</code> if the class is the interface class for a JSO
     * subtype.
     */
    public boolean isJsoIntf(String className) {
        return jsoIntfDescs.contains(toDescriptor(className));
    }

    /**
     * Performs rewriting transformations on a class.
     *
     * @param typeOracle a typeOracle modeling the user classes
     * @param className the name of the class
     * @param classBytes the bytes of the class
     * @param anonymousClassMap a map between the anonymous class names of java
     *          compiler used to compile code and jdt. Emma-specific.
     */
    public byte[] rewrite(TypeOracle typeOracle, String className, byte[] classBytes,
            Map<String, String> anonymousClassMap) {
        Event classBytesRewriteEvent = SpeedTracerLogger.start(DevModeEventType.CLASS_BYTES_REWRITE, "Class Name",
                className);
        String desc = toDescriptor(className);
        assert (!jsoIntfDescs.contains(desc));

        // The ASM model is to chain a bunch of visitors together.
        ClassWriter writer = new ClassWriter(0);
        ClassVisitor v = writer;

        // v = new CheckClassAdapter(v);
        // v = new TraceClassVisitor(v, new PrintWriter(System.out));
        v = new UseMirroredClasses(v, className);

        v = new RewriteSingleJsoImplDispatches(v, typeOracle, jsoData);

        v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper);

        if (jsoImplDescs.contains(desc)) {
            v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper, jsoData);
        }

        v = new RewriteJsniMethods(v, anonymousClassMap);

        if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_8) {
            // TODO(cromwellian) implement Retrolambda?
            v = new ForceClassVersion15(v);
        }

        new ClassReader(classBytes).accept(v, 0);
        classBytesRewriteEvent.end();
        return writer.toByteArray();
    }

    public byte[] writeJsoIntf(final String className, byte classBytes[]) {
        String desc = toDescriptor(className);
        assert (jsoIntfDescs.contains(desc));
        assert (jsoSuperDescs.containsKey(desc));
        List<String> superDescs = jsoSuperDescs.get(desc);
        assert (superDescs != null);
        assert (superDescs.size() > 0);

        // The ASM model is to chain a bunch of visitors together.
        ClassWriter writer = new ClassWriter(0);
        final ClassVisitor v = writer;

        // v = new CheckClassAdapter(v);
        // v = new TraceClassVisitor(v, new PrintWriter(System.out));

        String[] interfaces;
        // TODO(bov): something better than linear?
        if (superDescs.contains("java/lang/Object")) {
            interfaces = null;
        } else {
            interfaces = superDescs.toArray(new String[superDescs.size()]);
        }
        v.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, desc, null, "java/lang/Object",
                interfaces);
        if (classBytes != null) {
            // Java7 enforces innerclass/outerclass consistency. In order to fix this, copy from original
            ClassVisitor cv = new EmptyVisitor() {
                @Override
                public void visitInnerClass(String name, String outerName, String innerName, int access) {
                    // copy inner class table from original JSO to synthetic interface
                    v.visitInnerClass(name, outerName, innerName, access);
                }

                @Override
                public void visitOuterClass(String owner, String name, String desc) {
                    // copy outer class table from original JSO to synthetic interface
                    v.visitOuterClass(owner, name, desc);
                }
            };
            new ClassReader(classBytes).accept(cv, 0);
        }
        v.visitEnd();
        return writer.toByteArray();
    }
}