Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2009 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.googlecode.gwt.test.internal.rewrite;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
import com.google.gwt.dev.util.collect.Maps;
import com.google.gwt.dev.util.collect.Sets;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
import java.util.*;
/**
* Effects the renaming of {@code @SingleJsoImpl} methods from their original name to their mangled
* name. Let us call the original method an "unmangled method" and the new method a "mangled
* method". There are three steps in this process:
*
*
Within {@code @SingleJsoImpl} interfaces rename all unmangled methods to become mangled
* methods.
*
Within non-JSO classes containing a concrete implementation of an unmangled method, add a
* mangled method which is implemented as a simple trampoline to the unmangled method. (We don't do
* this in JSO classes here because the one-and-only trampoline lives in JavaScriptObject$ and is
* emitted in {@link WriteJsoImpl}).
*
Update all call sites targeting unmangled methods to target mangled methods instead, provided
* the caller is binding to the interface rather than a concrete type.
*
*/
public class RewriteSingleJsoImplDispatches extends ClassVisitor {
private class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM4, mv);
}
/*
* Implements objective #3: updates call sites to unmangled methods.
*/
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.INVOKEINTERFACE) {
if (jsoData.getSingleJsoIntfTypes().contains(owner)) {
// Simple case; referring directly to a SingleJso interface.
name = owner.replace('/', '_') + "_" + name;
assert jsoData.getMangledNames().contains(name) : "Missing " + name;
} else {
/*
* Might be referring to a subtype of a SingleJso interface:
*
* interface IA { void foo() }
*
* interface JA extends JSO implements IA;
*
* interface IB extends IA {}
*
* void bar() { ((IB) object).foo(); }
*/
outer:
for (String intf : computeAllInterfaces(owner)) {
if (jsoData.getSingleJsoIntfTypes().contains(intf)) {
/*
* Check that it really should be mangled and is not a reference to a method
* defined in a non-singleJso super-interface. If there are two
* super-interfaces that define methods with identical names and descriptors,
* the choice of implementation is undefined.
*/
String maybeMangled = intf.replace('/', '_') + "_" + name;
List methods = jsoData.getImplementations(maybeMangled);
if (methods != null) {
for (Method method : methods) {
/*
* Found a method with the right name, but we need to check the
* parameters and the return type. In order to do this, we'll look at the
* arguments and return type of the target method, removing the first
* argument, which is the instance.
*/
assert method.getArgumentTypes().length >= 1;
Type[] argumentTypes = new Type[method.getArgumentTypes().length - 1];
System.arraycopy(method.getArgumentTypes(), 1, argumentTypes, 0,
argumentTypes.length);
String maybeDescriptor = Type.getMethodDescriptor(
method.getReturnType(), argumentTypes);
if (maybeDescriptor.equals(desc)) {
name = maybeMangled;
break outer;
}
}
}
}
}
}
}
super.visitMethodInsn(opcode, owner, name, desc);
}
}
private String currentTypeName;
private final Set implementedMethods = new HashSet();
private boolean inSingleJsoImplInterfaceType;
private Map> intfNamesToAllInterfaces = Maps.create();
private final SingleJsoImplData jsoData;
private final TypeOracle typeOracle;
public RewriteSingleJsoImplDispatches(ClassVisitor v, TypeOracle typeOracle,
SingleJsoImplData jsoData) {
super(Opcodes.ASM4, v);
this.typeOracle = typeOracle;
this.jsoData = jsoData;
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
assert currentTypeName == null;
super.visit(version, access, name, signature, superName, interfaces);
/*
* This visitor would mangle JSO$ since it acts as a roll-up of all SingleJso types and the
* result would be repeated method definitions due to the trampoline methods this visitor
* would create.
*/
if (name.equals(OverlayTypesRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) {
return;
}
currentTypeName = name;
inSingleJsoImplInterfaceType = jsoData.getSingleJsoIntfTypes().contains(name);
/*
* Implements objective #2: non-JSO types that implement a SingleJsoImpl interface don't have
* their original instance methods altered. Instead, we add trampoline methods with mangled
* names that simply call over to the original methods.
*/
if (interfaces != null && (access & Opcodes.ACC_INTERFACE) == 0) {
Set toStub = computeAllInterfaces(interfaces);
toStub.retainAll(jsoData.getSingleJsoIntfTypes());
for (String stubIntr : toStub) {
writeTrampoline(stubIntr);
}
}
}
@Override
public void visitEnd() {
/*
* Add any missing methods that are defined by a super-interface, but that may be referenced
* via a more specific interface.
*/
if (inSingleJsoImplInterfaceType) {
for (String mangledName : toImplement(currentTypeName)) {
for (Method method : jsoData.getDeclarations(mangledName)) {
writeEmptyMethod(mangledName, method);
}
}
}
super.visitEnd();
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
/*
* Implements objective #2: Rename unmangled methods in a @SingleJsoImpl into mangled methods
* (except for clinit, LOL).
*/
if (inSingleJsoImplInterfaceType && !"".equals(name)) {
name = currentTypeName.replace('/', '_') + "_" + name;
implementedMethods.add(name);
}
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (mv == null) {
return null;
}
return new MyMethodVisitor(mv);
}
private Set computeAllInterfaces(String intfName) {
Set toReturn = intfNamesToAllInterfaces.get(intfName);
if (toReturn != null) {
return toReturn;
}
toReturn = Sets.create();
List q = new LinkedList();
JClassType intf = typeOracle.findType(intfName.replace('/', '.').replace('$', '.'));
/*
* If the interface's compilation unit wasn't retained due to an error, then it won't be
* available in the typeOracle for us to rewrite
*/
if (intf != null) {
q.add(intf);
}
while (!q.isEmpty()) {
intf = q.remove(0);
String resourceName = getResourceName(intf);
if (!toReturn.contains(resourceName)) {
toReturn = Sets.add(toReturn, resourceName);
Collections.addAll(q, intf.getImplementedInterfaces());
}
}
intfNamesToAllInterfaces = Maps.put(intfNamesToAllInterfaces, intfName, toReturn);
return toReturn;
}
private Set computeAllInterfaces(String[] interfaces) {
Set toReturn = new HashSet();
for (String intfName : interfaces) {
toReturn.addAll(computeAllInterfaces(intfName));
}
return toReturn;
}
private String getResourceName(JClassType type) {
if (type.getEnclosingType() != null) {
return getResourceName(type.getEnclosingType()) + "$" + type.getSimpleSourceName();
}
return type.getQualifiedSourceName().replace('.', '/');
}
/**
* Given a resource name of a class, find all mangled method names that must be implemented.
*/
private SortedSet toImplement(String typeName) {
String name = typeName.replace('/', '_');
String prefix = name + "_";
String suffix = name + "`";
SortedSet toReturn = new TreeSet();
for (String mangledName : jsoData.getMangledNames().subSet(prefix, suffix)) {
if (!implementedMethods.contains(mangledName)) {
toReturn.add(mangledName);
}
}
return toReturn;
}
private void writeEmptyMethod(String mangledMethodName, Method declMethod) {
MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT,
mangledMethodName, declMethod.getDescriptor(), null, null);
mv.visitEnd();
}
/**
* For regular Java objects that implement a SingleJsoImpl interface, write instance trampoline
* dispatchers for mangled method names to the implementing method.
*/
private void writeTrampoline(String stubIntr) {
/*
* This is almost the same kind of trampoline as the ones generated in WriteJsoImpl, however
* there are enough small differences between the semantics of the dispatches that would make
* a common implementation far more awkward than the duplication of code.
*/
for (String mangledName : toImplement(stubIntr)) {
for (Method method : jsoData.getDeclarations(mangledName)) {
Method toCall = new Method(method.getName(), method.getDescriptor());
// Must not be final
MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
mangledName, method.getDescriptor(), null, null);
if (mv != null) {
mv.visitCode();
/*
* It just so happens that the stack and local variable sizes are the same, but
* they're kept distinct to aid in clarity should the dispatch logic change.
*
* These start at 1 because we need to load "this" onto the stack
*/
int var = 1;
int size = 1;
// load this
mv.visitVarInsn(Opcodes.ALOAD, 0);
// then the rest of the arguments
for (Type t : toCall.getArgumentTypes()) {
size += t.getSize();
mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), var);
var += t.getSize();
}
// Make sure there's enough room for the return value
size = Math.max(size, toCall.getReturnType().getSize());
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, currentTypeName, toCall.getName(),
toCall.getDescriptor());
mv.visitInsn(toCall.getReturnType().getOpcode(Opcodes.IRETURN));
mv.visitMaxs(size, var);
mv.visitEnd();
}
}
}
}
}