com.google.gwt.dev.shell.CompilingClassLoader Maven / Gradle / Ivy
The newest version!
/*
* 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;
import com.google.gwt.core.client.GWTBridge;
import com.google.gwt.core.client.GwtScriptOnly;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
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.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompiledClass;
import com.google.gwt.dev.javac.JsniMethod;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.shell.rewrite.HasAnnotation;
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.JsniRef;
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.Name.InternalName;
import com.google.gwt.dev.util.Name.SourceOrBinaryName;
import com.google.gwt.dev.util.Util;
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.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.MapMaker;
import com.google.gwt.thirdparty.guava.common.primitives.Primitives;
import com.google.gwt.util.tools.Utility;
import java.beans.Beans;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
/**
* An isolated {@link ClassLoader} for running all user code. All user files are
* compiled from source code byte a {@link ByteCodeCompiler}. After compilation,
* some byte code rewriting is performed to support
* JavaScriptObject
and its subtypes.
*
* TODO: we should refactor this class to move the getClassInfoByDispId,
* getDispId, getMethodDispatch and putMethodDispatch into a separate entity
* since they really do not interact with the CompilingClassLoader
* functionality.
*/
public final class CompilingClassLoader extends ClassLoader implements
DispatchIdOracle {
/**
* Oracle that can answer questions about {@link DispatchClassInfo
* DispatchClassInfos}.
*/
private final class DispatchClassInfoOracle {
/**
* Class identifier to DispatchClassInfo mapping.
*/
private final ArrayList classIdToClassInfo = new ArrayList();
/**
* Binary or source class name to DispatchClassInfo map.
*/
private final Map classNameToClassInfo = new HashMap();
/**
* Clears out the contents of this oracle.
*/
public synchronized void clear() {
classIdToClassInfo.clear();
classNameToClassInfo.clear();
}
/**
* Returns the {@link DispatchClassInfo} for a given dispatch id.
*
* @param dispId dispatch id
* @return DispatchClassInfo for the requested dispatch id
*/
public synchronized DispatchClassInfo getClassInfoByDispId(int dispId) {
int classId = extractClassIdFromDispId(dispId);
return classIdToClassInfo.get(classId);
}
/**
* Returns the dispatch id for a given member reference. Member references
* can be encoded as: "@class::field" or "@class::method(typesigs)".
*
* @param jsniMemberRef a string encoding a JSNI member to use
* @return integer encoded as ((classId << 16) | memberId)
*/
public synchronized int getDispId(String jsniMemberRef) {
/*
* Map JS toString() onto the Java toString() method.
*/
if (jsniMemberRef.equals("toString")) {
jsniMemberRef = "@java.lang.Object::toString()";
}
JsniRef parsed = JsniRef.parse(jsniMemberRef);
if (parsed == null) {
logger.log(TreeLogger.ERROR, "Malformed JSNI reference '"
+ jsniMemberRef + "'; expect subsequent failures",
new NoSuchFieldError(jsniMemberRef));
return -1;
}
// Do the lookup by class name.
String className = parsed.className();
DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
if (dispClassInfo != null) {
String memberName = parsed.memberSignature();
/*
* Disallow the use of JSNI references to SingleJsoImpl interface
* methods. This policy is due to web-mode dispatch implementation
* details; resolving the JSNI reference wouldn't be just be a name
* replacement, instead it would be necessary to significantly alter the
* semantics of the hand-written JS.
*/
if (singleJsoImplTypes.contains(canonicalizeClassName(className))) {
logger.log(TreeLogger.ERROR,
"Invalid JSNI reference to SingleJsoImpl interface (" + className
+ "); consider using a trampoline. "
+ "Expect subsequent failures.", new NoSuchFieldError(
jsniMemberRef));
return -1;
}
int memberId = dispClassInfo.getMemberId(memberName);
if (memberId < 0) {
if (!className.startsWith("java.")) {
logger.log(TreeLogger.ERROR, "Member '" + memberName
+ "' in JSNI reference '" + jsniMemberRef
+ "' could not be found; expect subsequent failures",
new NoSuchFieldError(memberName));
}
}
return synthesizeDispId(dispClassInfo.getClassId(), memberId);
}
logger.log(TreeLogger.ERROR, "Class '" + className
+ "' in JSNI reference '" + jsniMemberRef
+ "' could not be found; expect subsequent failures",
new ClassNotFoundException(className));
return -1;
}
/**
* Extracts the class id from the dispatch id.
*
* @param dispId
* @return the classId encoded into this dispatch id
*/
private int extractClassIdFromDispId(int dispId) {
return (dispId >> 16) & 0xffff;
}
/**
* Returns the {@link java.lang.Class} instance for a given binary class
* name. It is important to avoid initializing the class because this would
* potentially cause initializers to be run in a different order than in web
* mode. Moreover, we may not have injected all of the JSNI code required to
* initialize the class.
*
* @param binaryClassName the binary name of a class
* @return {@link java.lang.Class} instance or null if the given binary
* class name could not be found
*/
private Class> getClassFromBinaryName(String binaryClassName) {
int dims = 0;
while (binaryClassName.endsWith("[]")) {
dims++;
binaryClassName = binaryClassName.substring(0,
binaryClassName.length() - 2);
}
Class> clazz = primitiveTypes.get(binaryClassName);
if (clazz == null) {
try {
clazz = Class.forName(binaryClassName, false,
CompilingClassLoader.this);
} catch (ClassNotFoundException e) {
}
}
// TODO(deprecation): remove this support eventually.
if (clazz == null && binaryClassName.length() == 1
&& "ZBCDFIJSV".indexOf(binaryClassName.charAt(0)) >= 0) {
clazz = getDeprecatedPrimitiveType(binaryClassName.charAt(0));
assert clazz != null;
}
if (dims > 0) {
return Array.newInstance(clazz, new int[dims]).getClass();
} else {
return clazz;
}
}
/**
* Returns the {@link java.lang.Class} object for a class that matches the
* source or binary name given.
*
* @param className binary or source name
* @return {@link java.lang.Class} instance, if found, or null
*/
private Class> getClassFromBinaryOrSourceName(String className) {
// Try the type oracle first
JClassType type = typeOracle.findType(SourceOrBinaryName.toSourceName(className));
if (type != null) {
// Use the type oracle to compute the exact binary name
String jniSig = type.getJNISignature();
jniSig = jniSig.substring(1, jniSig.length() - 1);
className = InternalName.toBinaryName(jniSig);
}
return getClassFromBinaryName(className);
}
/**
* Returns the {@link DispatchClassInfo} associated with the class name.
* Since we allow both binary and source names to be used in JSNI class
* references, we need to be able to deal with the fact that multiple
* permutations of the class name with regards to source or binary forms map
* on the same {@link DispatchClassInfo}.
*
* @param className binary or source name for a class
* @return {@link DispatchClassInfo} associated with the binary or source
* class name; null if there is none
*/
private DispatchClassInfo getClassInfoFromClassName(String className) {
DispatchClassInfo dispClassInfo = classNameToClassInfo.get(className);
if (dispClassInfo != null) {
// return the cached value
return dispClassInfo;
}
Class> cls = getClassFromBinaryOrSourceName(className);
if (cls == null) {
/*
* default to return null; mask the specific error and let the caller
* handle it
*/
return null;
}
// Map JSO type references to the appropriate impl class.
if (classRewriter.isJsoIntf(cls.getName())) {
cls = getClassFromBinaryName(cls.getName() + "$");
}
/*
* we need to create a new DispatchClassInfo since we have never seen this
* class before under any source or binary class name
*/
int classId = classIdToClassInfo.size();
dispClassInfo = new DispatchClassInfo(cls, classId);
classIdToClassInfo.add(dispClassInfo);
/*
* Whether we created a new DispatchClassInfo or not, we need to add a
* mapping for this name
*/
classNameToClassInfo.put(className, dispClassInfo);
return dispClassInfo;
}
@Deprecated
private Class> getDeprecatedPrimitiveType(char c) {
switch (c) {
case 'Z':
return boolean.class;
case 'B':
return byte.class;
case 'C':
return char.class;
case 'D':
return double.class;
case 'F':
return float.class;
case 'I':
return int.class;
case 'J':
return long.class;
case 'S':
return short.class;
case 'V':
return void.class;
default:
return null;
}
}
/**
* Synthesizes a dispatch identifier for the given class and member ids.
*
* @param classId class index
* @param memberId member index
* @return dispatch identifier for the given class and member ids
*/
private int synthesizeDispId(int classId, int memberId) {
return (classId << 16) | memberId;
}
}
/**
* A ClassLoader that will delegate to a parent ClassLoader and fall back to
* loading bytecode as resources from an alternate parent ClassLoader.
*/
private static class MultiParentClassLoader extends ClassLoader {
private final ClassLoader resources;
public MultiParentClassLoader(ClassLoader parent, ClassLoader resources) {
super(parent);
assert parent != null;
this.resources = resources;
}
@Override
protected synchronized Class> findClass(String name)
throws ClassNotFoundException {
String resourceName = name.replace('.', '/') + ".class";
URL url = resources.getResource(resourceName);
if (url == null) {
throw new ClassNotFoundException();
}
byte[] bytes = Util.readURLAsBytes(url);
return defineClass(name, bytes, 0, bytes.length);
}
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
try {
Class c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
return getParent().loadClass(name);
} catch (Throwable t) {
// Make a second attempt not only on ClassNotFoundExceptions, but also errors like
// ClassCircularityError
Class c = findClass(name);
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
/**
* Implements {@link InstanceMethodOracle} on behalf of the
* {@link HostedModeClassRewriter}. Implemented using {@link TypeOracle}.
*/
private class MyInstanceMethodOracle implements InstanceMethodOracle {
private final Map> signatureToDeclaringClasses = new HashMap>();
public MyInstanceMethodOracle(Set jsoTypes,
JClassType javaLangObject, SingleJsoImplData jsoData) {
// 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 declaringClasses = new HashSet();
signatureToDeclaringClasses.put(signature, declaringClasses);
declaringClasses.add(javaLangObject);
}
}
}
@Override
public String findOriginalDeclaringClass(String desc, String signature) {
// Lookup the method.
Set 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 declaringClasses = signatureToDeclaringClasses.get(signature);
if (declaringClasses == null) {
declaringClasses = new HashSet();
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 mangledNames = new TreeSet();
private final Map> mangledNamesToDeclarations = new HashMap>();
private final Map> mangledNamesToImplementations = new HashMap>();
private final Set unmodifiableIntfNames = Collections.unmodifiableSet(singleJsoImplTypes);
private final SortedSet unmodifiableNames = Collections.unmodifiableSortedSet(mangledNames);
public MySingleJsoImplData() {
// 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 += ")";
org.objectweb.asm.commons.Method declaration = org.objectweb.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 += ")";
org.objectweb.asm.commons.Method toImplement = org.objectweb.asm.commons.Method.getMethod(decl);
addToMap(mangledNamesToImplementations, mangledName, toImplement);
}
}
}
if (logger.isLoggable(TreeLogger.SPAM)) {
TreeLogger dumpLogger = logger.branch(TreeLogger.SPAM,
"SingleJsoImpl method mappings");
for (Map.Entry> entry : mangledNamesToImplementations.entrySet()) {
dumpLogger.log(TreeLogger.SPAM, entry.getKey() + " -> " + entry.getValue());
}
}
}
@Override
public List getDeclarations(
String mangledName) {
List toReturn = mangledNamesToDeclarations.get(mangledName);
return toReturn == null ? null : Collections.unmodifiableList(toReturn);
}
@Override
public List getImplementations(
String mangledName) {
List toReturn = mangledNamesToImplementations.get(mangledName);
return toReturn == null ? toReturn
: Collections.unmodifiableList(toReturn);
}
@Override
public SortedSet getMangledNames() {
return unmodifiableNames;
}
@Override
public Set getSingleJsoIntfTypes() {
return unmodifiableIntfNames;
}
/**
* Assumes that the usual case is a 1:1 mapping.
*/
private void addToMap(Map> map, K key, V value) {
List list = map.get(key);
if (list == null) {
map.put(key, Lists.create(value));
} else {
List maybeOther = Lists.add(list, value);
if (maybeOther != list) {
map.put(key, maybeOther);
}
}
}
/**
* Looks for a concrete implementation of intfMethod
in
* implementingType
.
*/
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;
}
}
/**
* Only loads bootstrap classes, specifically excluding classes from the classpath.
*/
private static final ClassLoader bootstrapClassLoader = new ClassLoader(null) { };
/**
* The names of the bridge classes.
*/
private static final Map> BRIDGE_CLASS_NAMES = new HashMap>();
/**
* The set of classes exposed into user space that actually live in hosted
* space (thus, they bridge across the spaces).
*/
private static final Class>[] BRIDGE_CLASSES = new Class>[]{
// Have to include the shared GWTBridge class since the client one
// inherits from it, otherwise we get verify errors
ShellJavaScriptHost.class, GWTBridge.class, com.google.gwt.core.shared.GWTBridge.class};
private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump");
private static final String CLASS_DUMP_PATH = System.getProperty(
"gwt.dev.classDumpPath", "rewritten-classes");
private static final String JACOCO_ENTRYPOINT = "org.jacoco.core.JaCoCo";
private static boolean emmaAvailable = false;
private static EmmaStrategy emmaStrategy;
/**
* Caches the byte code for {@link JavaScriptHost}.
*/
private static byte[] javaScriptHostBytes;
private static final Map> primitiveTypes;
static {
ImmutableMap.Builder> builder = ImmutableMap.builder();
for (Class> klass : Primitives.allPrimitiveTypes()) {
builder.put(klass.getSimpleName(), klass);
}
primitiveTypes = builder.build();
}
static {
for (Class> c : BRIDGE_CLASSES) {
BRIDGE_CLASS_NAMES.put(c.getName(), c);
}
/*
* Specific support for bridging to Emma since the user classloader is
* generally completely isolated.
*
* We are looking for a specific emma class "com.vladium.emma.rt.RT". If
* that changes in the future, this code would need to be updated as well.
*/
try {
Class> emmaBridge = Class.forName(EmmaStrategy.EMMA_RT_CLASSNAME,
false, Thread.currentThread().getContextClassLoader());
BRIDGE_CLASS_NAMES.put(EmmaStrategy.EMMA_RT_CLASSNAME, emmaBridge);
emmaAvailable = true;
} catch (ClassNotFoundException ignored) {
}
emmaStrategy = EmmaStrategy.get(emmaAvailable);
/* Bridging Jacoco's Offline entry point. */
try {
Class> jacoco = Class.forName(JACOCO_ENTRYPOINT,
false, Thread.currentThread().getContextClassLoader());
String offlineName = (String) jacoco.getDeclaredField("RUNTIMEPACKAGE").get(jacoco)
+ ".Offline";
Class> offlineBridge = Class.forName(offlineName,
false, Thread.currentThread().getContextClassLoader());
BRIDGE_CLASS_NAMES.put(offlineName, offlineBridge);
} catch (Exception ignored) {
}
}
private static void classDump(String name, byte[] bytes) {
String packageName, className;
int pos = name.lastIndexOf('.');
if (pos < 0) {
packageName = "";
className = name;
} else {
packageName = name.substring(0, pos);
className = name.substring(pos + 1);
}
File dir = new File(CLASS_DUMP_PATH + File.separator
+ packageName.replace('.', File.separatorChar));
if (!dir.exists()) {
// No need to check mkdirs result because an IOException will occur anyway
dir.mkdirs();
}
File file = new File(dir, className + ".class");
FileOutputStream fileOutput = null;
try {
fileOutput = new FileOutputStream(file);
fileOutput.write(bytes);
fileOutput.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutput != null) {
try {
fileOutput.close();
} catch (IOException e) {
// oh well, we tried
}
}
}
}
/**
* Magic: {@link JavaScriptHost} was never compiled because it's a part of the
* hosted mode infrastructure. However, unlike {@link #BRIDGE_CLASSES},
* {@code JavaScriptHost} needs a separate copy per inside the ClassLoader for
* each module.
*/
private static void ensureJavaScriptHostBytes(TreeLogger logger)
throws UnableToCompleteException {
if (javaScriptHostBytes != null) {
return;
}
String className = JavaScriptHost.class.getName();
try {
String path = className.replace('.', '/') + ".class";
ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL url = cl.getResource(path);
if (url != null) {
javaScriptHostBytes = getClassBytesFromStream(url.openStream());
} else {
logger.log(TreeLogger.ERROR,
"Could not find required bootstrap class '" + className
+ "' in the classpath", null);
throw new UnableToCompleteException();
}
} catch (IOException e) {
logger.log(TreeLogger.ERROR,
"Error reading class bytes for " + className, e);
throw new UnableToCompleteException();
}
}
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 byte[] getClassBytesFromStream(InputStream is)
throws IOException {
try {
byte classBytes[] = new byte[is.available()];
int read = 0;
while (read < classBytes.length) {
read += is.read(classBytes, read, classBytes.length - read);
}
return classBytes;
} finally {
Utility.close(is);
}
}
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;
}
/**
* The set of units whose JSNI has already been injected.
*/
private Set alreadyInjected = new HashSet();
private final HostedModeClassRewriter classRewriter;
private CompilationState compilationState;
private final DispatchClassInfoOracle dispClassInfoOracle = new DispatchClassInfoOracle();
private Class> gwtClass, javaScriptHostClass;
/**
* Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
*/
private boolean isInjectingClass = false;
private final ReentrantLock loadLock = new ReentrantLock();
private final TreeLogger logger;
private final Set scriptOnlyClasses = new HashSet();
private ClassLoader scriptOnlyClassLoader;
private ShellJavaScriptHost shellJavaScriptHost;
private final Set singleJsoImplTypes = new HashSet();
/**
* Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
*/
private Stack toInject = new Stack();
private final TypeOracle typeOracle;
private final Map