Adds Emma integration for client classes running in hosted mode. Here are the key parts:
1) Bridge Emma's RT class. This is necessary to allow classes living in the CCL to access the outside world, in this case the Emma coverage data hook points.
2) Allow instrumented classes to load from disk. This is necessary to pick up the modifications to pre-instrumented classes, which is the most common use case with Emma. This is the only case we currently support, although tobyr has some indication that Java class transformers might also work. We only conditionally read class files off disk because it's slower; you have to stat the class and java files to compare dates, and then actually read the bytes, whereas normally the compiled bytes are already in RAM. Instant hosted mode should level the playing field by always preferring disk.
Review by: bobv
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3622 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 31b9f57..ef4dd2c 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -354,10 +354,25 @@
*/
private static byte[] javaScriptHostBytes;
+ private static EmmaStrategy emmaStrategy;
+
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.
+ */
+ boolean emmaIsAvailable = false;
+ try {
+ Class<?> emmaBridge = Class.forName(EmmaStrategy.EMMA_RT_CLASSNAME,
+ false, Thread.currentThread().getContextClassLoader());
+ BRIDGE_CLASS_NAMES.put(EmmaStrategy.EMMA_RT_CLASSNAME, emmaBridge);
+ emmaIsAvailable = true;
+ } catch (ClassNotFoundException ignored) {
+ }
+ emmaStrategy = EmmaStrategy.get(emmaIsAvailable);
}
private static void classDump(String name, byte[] bytes) {
@@ -637,6 +652,8 @@
injectJsniFor(compiledClass);
byte[] classBytes = compiledClass.getBytes();
+ classBytes = emmaStrategy.getEmmaClassBytes(classBytes, lookupClassName,
+ compiledClass.getUnit().getLastModified());
if (classRewriter != null) {
byte[] newBytes = classRewriter.rewrite(className, classBytes);
if (CLASS_DUMP) {
diff --git a/dev/core/src/com/google/gwt/dev/shell/EmmaStrategy.java b/dev/core/src/com/google/gwt/dev/shell/EmmaStrategy.java
new file mode 100644
index 0000000..5cc8fc5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/EmmaStrategy.java
@@ -0,0 +1,88 @@
+/*
+ * 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.dev.util.Util;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Provides various strategies for emma integration based on runtime detection.
+ */
+abstract class EmmaStrategy {
+
+ private static class NoEmmaStrategy extends EmmaStrategy {
+ public byte[] getEmmaClassBytes(byte[] classBytes, String slashedName,
+ long unitLastModified) {
+ return classBytes;
+ }
+ }
+
+ private static class PreinstrumentedEmmaStrategy extends EmmaStrategy {
+ public byte[] getEmmaClassBytes(byte[] classBytes, String slashedName,
+ long unitLastModified) {
+ // Check for an existing class on the classpath.
+ URL url = Thread.currentThread().getContextClassLoader().getResource(
+ slashedName + ".class");
+ if (url != null) {
+ // We found it on the class path.
+ try {
+ URLConnection conn = url.openConnection();
+ if (conn.getLastModified() >= unitLastModified) {
+ // It's as new as the source file, let's use it.
+ byte[] result = Util.readURLConnectionAsBytes(conn);
+ if (result != null) {
+ return result;
+ }
+ // Fall through.
+ }
+ // Fall through.
+ } catch (IOException ignored) {
+ // Fall through.
+ }
+ }
+
+ // Just return what we got.
+ return classBytes;
+ }
+ }
+
+ /**
+ * Classname for Emma's RT, to enable bridging.
+ */
+ public static final String EMMA_RT_CLASSNAME = "com.vladium.emma.rt.RT";
+
+ /**
+ * Gets the emma classloading strategy.
+ */
+ public static EmmaStrategy get(boolean emmaIsAvailable) {
+ /*
+ * Theoretically, emmarun could be using an instrumented ClassLoader, but in
+ * practice we haven't been able to make GWT run at all in this case.
+ */
+ if (!emmaIsAvailable) {
+ return new NoEmmaStrategy();
+ } else {
+ return new PreinstrumentedEmmaStrategy();
+ }
+ }
+
+ public abstract byte[] getEmmaClassBytes(byte[] classBytes,
+ String slashedName, long unitLastModified);
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index d224eb2..6f3d637 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -599,11 +599,31 @@
* @return null if the file could not be read
*/
public static byte[] readURLAsBytes(URL url) {
+ try {
+ URLConnection conn = url.openConnection();
+ conn.setUseCaches(false);
+ return readURLConnectionAsBytes(conn);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @return null if the file could not be read
+ */
+ public static char[] readURLAsChars(URL url) {
+ byte[] bytes = readURLAsBytes(url);
+ if (bytes != null) {
+ return toString(bytes, DEFAULT_ENCODING).toCharArray();
+ }
+
+ return null;
+ }
+
+ public static byte[] readURLConnectionAsBytes(URLConnection connection) {
// ENH: add a weak cache that has an additional check against the file date
InputStream input = null;
try {
- URLConnection connection = url.openConnection();
- connection.setUseCaches(false);
input = connection.getInputStream();
int contentLength = connection.getContentLength();
if (contentLength < 0) {
@@ -619,19 +639,6 @@
}
/**
- * @return null if the file could not be read
- */
- public static char[] readURLAsChars(URL url) {
- // ENH: add a weak cache that has an additional check against the file date
- byte[] bytes = readURLAsBytes(url);
- if (bytes != null) {
- return toString(bytes, DEFAULT_ENCODING).toCharArray();
- }
-
- return null;
- }
-
- /**
* Deletes a file or recursively deletes a directory.
*
* @param file the file to delete, or if this is a directory, the directory