Package com.googlecode.gwt.test.internal.rewrite

Source Code of com.googlecode.gwt.test.internal.rewrite.OverlayTypesRewriter$MySingleJsoImplData

package com.googlecode.gwt.test.internal.rewrite;

import java.util.ArrayList;
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;
import java.util.TreeSet;

import com.google.gwt.core.client.GwtScriptOnly;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.asm.ClassReader;
import com.google.gwt.dev.asm.ClassVisitor;
import com.google.gwt.dev.asm.ClassWriter;
import com.google.gwt.dev.asm.Opcodes;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.shell.JsValueGlue;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.collect.Lists;
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 com.googlecode.gwt.test.GwtTreeLogger;

/**
* This class performs any and all byte code rewriting needed to make Overlay types work with
* gwt-test-utils.
* <ol>
* <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 the impl class.</li>
* <li>JavaScriptObject$ implements all the interface types and is the only instantiable type.</li>
* </ol>
* <strong>For internal use only.</strong>
*
* @see RewriteRefsToJsoClasses
* @see WriteJsoImpl
*/
public class OverlayTypesRewriter {

   /**
    * Implements {@link InstanceMethodOracle} on behalf of the {@link HostedModeClassRewriter}.
    * Implemented using {@link TypeOracle}.
    */
   private class MyInstanceMethodOracle implements InstanceMethodOracle {

      private final Map<String, Set<JClassType>> signatureToDeclaringClasses = new HashMap<String, Set<JClassType>>();

      public MyInstanceMethodOracle(Set<JClassType> jsoTypes, JClassType javaLangObject) {
         // Record that the JSO implements its own methods
         for (JClassType type : jsoTypes) {
            for (JMethod method : type.getMethods()) {
               if (!method.isStatic()) {
                  assert !method.isAbstract() : "Abstract method in JSO type " + method;
                  add(type, method);
               }
            }
         }

         /*
          * Record the implementing types for methods defined in SingleJsoImpl interfaces. We have
          * to make this pass because of possible variance in the return types between the abstract
          * method declaration in the interface and the concrete method.
          */
         for (String intfName : jsoData.getSingleJsoIntfTypes()) {
            // We only store the name in the data block to keep it lightweight
            JClassType intf = typeOracle.findType(Name.InternalName.toSourceName(intfName));
            JClassType jso = typeOracle.getSingleJsoImpl(intf);
            for (JMethod method : intf.getMethods()) {
               JClassType implementingJso = findImplementingTypeForMethod(jso, method);
               assert implementingJso != null : "Jso should contain method: "
                        + method.getJsniSignature();
               add(implementingJso, method);
            }
         }

         // Object clobbers everything.
         for (JMethod method : javaLangObject.getMethods()) {
            if (!method.isStatic()) {
               String signature = createSignature(method);
               Set<JClassType> declaringClasses = new HashSet<JClassType>();
               signatureToDeclaringClasses.put(signature, declaringClasses);
               declaringClasses.add(javaLangObject);
            }
         }
      }

      public String findOriginalDeclaringClass(String desc, String signature) {
         // Lookup the method.
         Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
         assert declaringClasses != null : "No classes for " + signature;
         if (declaringClasses.size() == 1) {
            // Shortcut: if there's only one answer, it must be right.
            return createDescriptor(declaringClasses.iterator().next());
         }
         // Must check for assignability.
         String sourceName = desc.replace('/', '.');
         sourceName = sourceName.replace('$', '.');
         JClassType declaredType = typeOracle.findType(sourceName);

         // Check if I declare this directly.
         if (declaringClasses.contains(declaredType)) {
            return desc;
         }

         // Check to see what type I am assignable to.
         for (JClassType possibleSupertype : declaringClasses) {
            if (declaredType.isAssignableTo(possibleSupertype)) {
               return createDescriptor(possibleSupertype);
            }
         }
         throw new IllegalArgumentException("Could not resolve signature '" + signature
                  + "' from class '" + desc + "'");
      }

      /**
       * Record that a given JSO type contains the concrete implementation of a (possibly abstract)
       * method.
       */
      private void add(JClassType type, JMethod method) {
         String signature = createSignature(method);
         Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
         if (declaringClasses == null) {
            declaringClasses = new HashSet<JClassType>();
            signatureToDeclaringClasses.put(signature, declaringClasses);
         }
         declaringClasses.add(type);
      }

      private String createDescriptor(JClassType type) {
         String jniSignature = type.getJNISignature();
         return jniSignature.substring(1, jniSignature.length() - 1);
      }

      private String createSignature(JMethod method) {
         StringBuffer sb = new StringBuffer(method.getName());
         sb.append('(');
         for (JParameter param : method.getParameters()) {
            sb.append(param.getType().getJNISignature());
         }
         sb.append(')');
         sb.append(method.getReturnType().getJNISignature());
         String signature = sb.toString();
         return signature;
      }
   }

   /**
    * Cook up the data we need to support JSO subtypes that implement interfaces with methods. This
    * includes the set of SingleJsoImpl interfaces actually implemented by a JSO type, the mangled
    * method names, and the names of the Methods that should actually implement the virtual
    * functions.
    *
    * Given the current implementation of JSO$ and incremental execution of rebinds, it's not
    * possible for Generators to produce additional JavaScriptObject subtypes, so this data can
    * remain static.
    */
   private class MySingleJsoImplData implements SingleJsoImplData {
      private final SortedSet<String> mangledNames = new TreeSet<String>();
      private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToDeclarations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>();
      private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToImplementations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>();
      private final Set<String> unmodifiableIntfNames = Collections.unmodifiableSet(singleJsoImplTypes);
      private final SortedSet<String> unmodifiableNames = Collections.unmodifiableSortedSet(mangledNames);

      public MySingleJsoImplData(TypeOracle typeOracle) {
         // Loop over all interfaces with JSO implementations
         typeLoop : for (JClassType type : typeOracle.getSingleJsoImplInterfaces()) {
            assert type.isInterface() == type : "Expecting interfaces only";

            /*
             * By preemptively adding all possible mangled names by which a method could be called,
             * we greatly simplify the logic necessary to rewrite the call-site.
             *
             * interface A {void m();}
             *
             * interface B extends A {void z();}
             *
             * becomes
             *
             * c_g_p_A_m() -> JsoA$.m$()
             *
             * c_g_p_B_m() -> JsoA$.m$()
             *
             * c_g_p_B_z() -> JsoB$.z$()
             */
            for (JMethod intfMethod : type.getOverridableMethods()) {
               assert intfMethod.isAbstract() : "Expecting only abstract methods";

               /*
                * It is necessary to locate the implementing type on a per-method basis. Consider
                * the case of
                *
                * @SingleJsoImpl interface C extends A, B {}
                *
                * Methods inherited from interfaces A and B must be dispatched to their respective
                * JSO implementations.
                */
               JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());

               if (implementingType == null
                        || implementingType.isAnnotationPresent(GwtScriptOnly.class)) {
                  /*
                   * This means that there is no concrete implementation of the interface by a JSO.
                   * Any implementation that might be created by a Generator won't be a JSO subtype,
                   * so we'll just ignore it as an actionable type. Were Generators ever able to
                   * create new JSO subtypes, we'd have to speculatively rewrite the callsite.
                   */
                  continue typeLoop;
               }

               /*
                * Record the type as being actionable.
                */
               singleJsoImplTypes.add(canonicalizeClassName(getBinaryName(type)));

               /*
                * The mangled name adds the current interface like
                *
                * com_foo_Bar_methodName
                */
               String mangledName = getBinaryName(type).replace('.', '_') + "_"
                        + intfMethod.getName();
               mangledNames.add(mangledName);

               /*
                * Handle virtual overrides by finding the method that we would normally invoke and
                * using its declaring class as the dispatch target.
                */
               JMethod implementingMethod;
               while ((implementingMethod = findOverloadUsingErasure(implementingType, intfMethod)) == null) {
                  implementingType = implementingType.getSuperclass();
               }
               // implementingmethod and implementingType cannot be null here

               /*
                * Create a pseudo-method declaration for the interface method. This should look
                * something like
                *
                * ReturnType method$ (ParamType, ParamType)
                *
                * This must be kept in sync with the WriteJsoImpl class.
                */
               {
                  String decl = getBinaryOrPrimitiveName(intfMethod.getReturnType().getErasedType())
                           + " " + intfMethod.getName() + "(";
                  for (JParameter param : intfMethod.getParameters()) {
                     decl += ",";
                     decl += getBinaryOrPrimitiveName(param.getType().getErasedType());
                  }
                  decl += ")";

                  com.google.gwt.dev.asm.commons.Method declaration = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
                  addToMap(mangledNamesToDeclarations, mangledName, declaration);
               }

               /*
                * Cook up the a pseudo-method declaration for the concrete type. This should look
                * something like
                *
                * ReturnType method$ (JsoType, ParamType, ParamType)
                *
                * This must be kept in sync with the WriteJsoImpl class.
                */
               {
                  String returnName = getBinaryOrPrimitiveName(implementingMethod.getReturnType().getErasedType());
                  String jsoName = getBinaryOrPrimitiveName(implementingType);

                  String decl = returnName + " " + intfMethod.getName() + "$ (" + jsoName;
                  for (JParameter param : implementingMethod.getParameters()) {
                     decl += ",";
                     decl += getBinaryOrPrimitiveName(param.getType().getErasedType());
                  }
                  decl += ")";

                  com.google.gwt.dev.asm.commons.Method toImplement = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
                  addToMap(mangledNamesToImplementations, mangledName, toImplement);
               }
            }
         }

         TreeLogger logger = GwtTreeLogger.get();
         if (logger.isLoggable(TreeLogger.SPAM)) {
            TreeLogger dumpLogger = logger.branch(TreeLogger.SPAM, "SingleJsoImpl method mappings");
            for (Map.Entry<String, List<com.google.gwt.dev.asm.commons.Method>> entry : mangledNamesToImplementations.entrySet()) {
               dumpLogger.log(TreeLogger.SPAM, entry.getKey() + " -> " + entry.getValue());
            }
         }
      }

      public List<com.google.gwt.dev.asm.commons.Method> getDeclarations(String mangledName) {
         List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToDeclarations.get(mangledName);
         return toReturn == null ? null : Collections.unmodifiableList(toReturn);
      }

      public List<com.google.gwt.dev.asm.commons.Method> getImplementations(String mangledName) {
         List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToImplementations.get(mangledName);
         return toReturn == null ? toReturn : Collections.unmodifiableList(toReturn);
      }

      public SortedSet<String> getMangledNames() {
         return unmodifiableNames;
      }

      public Set<String> getSingleJsoIntfTypes() {
         return unmodifiableIntfNames;
      }

      /**
       * Assumes that the usual case is a 1:1 mapping.
       */
      private <K, V> void addToMap(Map<K, List<V>> map, K key, V value) {
         List<V> list = map.get(key);
         if (list == null) {
            map.put(key, Lists.create(value));
         } else {
            List<V> maybeOther = Lists.add(list, value);
            if (maybeOther != list) {
               map.put(key, maybeOther);
            }
         }
      }

      /**
       * Looks for a concrete implementation of <code>intfMethod</code> in
       * <code>implementingType</code>.
       */
      private JMethod findOverloadUsingErasure(JClassType implementingType, JMethod intfMethod) {

         int numParams = intfMethod.getParameters().length;
         JType[] erasedTypes = new JType[numParams];
         for (int i = 0; i < numParams; i++) {
            erasedTypes[i] = intfMethod.getParameters()[i].getType().getErasedType();
         }

         outer : for (JMethod method : implementingType.getOverloads(intfMethod.getName())) {
            JParameter[] params = method.getParameters();
            if (params.length != numParams) {
               continue;
            }
            for (int i = 0; i < numParams; i++) {
               if (params[i].getType().getErasedType() != erasedTypes[i]) {
                  continue outer;
               }
            }
            return method;
         }
         return null;
      }
   }

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

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

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

   private static JClassType findImplementingTypeForMethod(JClassType type, JMethod method) {
      JType[] methodParamTypes = method.getErasedParameterTypes();
      while (type != null) {
         for (JMethod candidate : type.getMethods()) {
            if (hasMatchingErasedSignature(method, methodParamTypes, candidate)) {
               return type;
            }
         }
         type = type.getSuperclass();
      }
      return null;
   }

   private static boolean hasMatchingErasedSignature(JMethod a, JType[] aParamTypes, JMethod b) {
      if (!a.getName().equals(b.getName())) {
         return false;
      }

      JType[] bParamTypes = b.getErasedParameterTypes();
      if (aParamTypes.length != bParamTypes.length) {
         return false;
      }

      for (int i = 0; i < aParamTypes.length; ++i) {
         if (aParamTypes[i] != bParamTypes[i]) {
            return false;
         }
      }

      return true;
   }

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

   private final CompilationState compilationState;

   private final SingleJsoImplData jsoData;
   /**
    * 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;

   /**
    * 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 final InstanceMethodOracle mapper;

   private final Set<String> singleJsoImplTypes = new HashSet<String>();

   private final TypeOracle typeOracle;

   /**
    *
    * @param compilationState the name of the GWT module under test
    * @param jsoType the type of JavaScriptObject
    */
   public OverlayTypesRewriter(CompilationState compilationState, JClassType jsoType) {

      // Create a set of binary names.
      Set<JClassType> jsoTypes = new HashSet<JClassType>();
      JClassType[] jsoSubtypes = jsoType.getSubtypes();
      Collections.addAll(jsoTypes, jsoSubtypes);
      jsoTypes.add(jsoType);

      Set<String> jsoTypeNames = new HashSet<String>();
      Map<String, List<String>> jsoSuperTypes = new HashMap<String, List<String>>();
      for (JClassType type : jsoTypes) {
         List<String> types = new ArrayList<String>();
         types.add(getBinaryName(type.getSuperclass()));
         for (JClassType impl : type.getImplementedInterfaces()) {
            types.add(getBinaryName(impl));
         }

         String binaryName = getBinaryName(type);
         jsoTypeNames.add(binaryName);
         jsoSuperTypes.put(binaryName, types);
      }

      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 : jsoTypeNames) {
         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));
      }

      // FIXME: RegExp extends JavaScriptObject at runtime, don't know why...
      String notJsoDesc = toDescriptor("com.google.gwt.regexp.shared.RegExp");
      buildJsoIntfDescs.remove(notJsoDesc);
      buildJsoImplDescs.remove(notJsoDesc);
      buildJsoSuperDescs.remove(notJsoDesc);
      // FIXME: MatchResult extends JavaScriptObject at runtime, don't know
      // why...
      notJsoDesc = toDescriptor("com.google.gwt.regexp.shared.MatchResult");
      buildJsoIntfDescs.remove(notJsoDesc);
      buildJsoImplDescs.remove(notJsoDesc);
      buildJsoSuperDescs.remove(notJsoDesc);
      // FIXME: SplitResult extends JavaScriptObject at runtime, don't know
      // why...
      notJsoDesc = toDescriptor("com.google.gwt.regexp.shared.SplitResult");
      buildJsoIntfDescs.remove(notJsoDesc);
      buildJsoImplDescs.remove(notJsoDesc);
      buildJsoSuperDescs.remove(notJsoDesc);

      this.compilationState = compilationState;
      this.typeOracle = compilationState.getTypeOracle();
      this.jsoIntfDescs = Collections.unmodifiableSet(buildJsoIntfDescs);
      this.jsoImplDescs = Collections.unmodifiableSet(buildJsoImplDescs);
      this.jsoSuperDescs = Collections.unmodifiableMap(buildJsoSuperDescs);
      this.jsoData = new MySingleJsoImplData(typeOracle);
      this.mapper = new MyInstanceMethodOracle(jsoTypes, typeOracle.getJavaLangObject());
   }

   /**
    * Convert a binary class name into a resource-like name.
    */
   public String canonicalizeClassName(String className) {
      String lookupClassName = className.replace('.', '/');
      // A JSO impl class ends with $, strip it
      if (isJsoImpl(className)) {
         lookupClassName = lookupClassName.substring(0, lookupClassName.length() - 1);
      }
      return lookupClassName;
   }

   public CompilationState getCompilationState() {
      return compilationState;
   }

   /**
    * 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 className the name of the class
    * @param classBytes the bytes of the class
    */
   public byte[] rewrite(String className, byte[] classBytes) {
      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);
      }

      if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6) {
         v = new ForceClassVersion15(v);
      }

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

   public byte[] writeJsoIntf(String className) {
      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);
      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);
      v.visitEnd();
      return writer.toByteArray();
   }

   private String getBinaryName(JClassType type) {
      String name = type.getPackage().getName() + '.';
      name += type.getName().replace('.', '$');
      return name;
   }

   private String getBinaryOrPrimitiveName(JType type) {
      JArrayType asArray = type.isArray();
      JClassType asClass = type.isClassOrInterface();
      JPrimitiveType asPrimitive = type.isPrimitive();
      if (asClass != null) {
         return getBinaryName(asClass);
      } else if (asPrimitive != null) {
         return asPrimitive.getQualifiedSourceName();
      } else if (asArray != null) {
         JType componentType = asArray.getComponentType();
         return getBinaryOrPrimitiveName(componentType) + "[]";
      } else {
         throw new InternalCompilerException("Cannot create binary name for "
                  + type.getQualifiedSourceName());
      }
   }

}
TOP

Related Classes of com.googlecode.gwt.test.internal.rewrite.OverlayTypesRewriter$MySingleJsoImplData

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.