Fix GC handling for HtmlUnit in development mode.
Patch by: jat
Review by: amitmanjhi
git-svn-id: https://google-web-toolkit.googlecode.com/svn/branches/farewellSwt@6215 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
index 99629c7..2c589fd 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
@@ -29,16 +29,9 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
import java.net.Socket;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.Map;
+import java.util.Collections;
import java.util.Set;
-import java.util.TreeMap;
/**
*
@@ -48,7 +41,7 @@
/**
* Class representing a reference to a Java object.
*/
- public static class JavaObjectRef {
+ public static class JavaObjectRef implements RemoteObjectRef {
private int refId;
public JavaObjectRef(int refId) {
@@ -60,6 +53,15 @@
}
@Override
+ public int hashCode() {
+ return refId;
+ }
+
+ public boolean isException() {
+ return refId < 0;
+ }
+
+ @Override
public String toString() {
return "JavaObjectRef(ref=" + refId + ")";
}
@@ -68,14 +70,8 @@
/**
* Class representing a reference to a JS object.
*/
- public static class JsObjectRef {
+ public static class JsObjectRef implements RemoteObjectRef {
- // TODO: refactor and remove this method.
- public static void checkIdMap(int refId) {
- assert !JSOBJECT_ID_MAP.get().containsKey(refId)
- || (JSOBJECT_ID_MAP.get().get(refId).get() == null);
- }
-
private int refId;
public JsObjectRef(int refId) {
@@ -110,13 +106,77 @@
/**
* Enumeration of message type ids.
*
- * NOTE: order is important as this defines the ordinals used in the wire
- * protocol.
+ * <p>Ids are used instead of relying on the ordinal to avoid sychronization
+ * problems with the client.
*/
public enum MessageType {
- INVOKE, RETURN, OLD_LOAD_MODULE, QUIT, LOAD_JSNI, INVOKE_SPECIAL, FREE_VALUE,
- FATAL_ERROR, CHECK_VERSIONS, PROTOCOL_VERSION, CHOOSE_TRANSPORT,
- SWITCH_TRANSPORT, LOAD_MODULE;
+ /**
+ * A message to invoke a method on the other side of the wire. Note that
+ * the messages are asymmetric -- see {@link InvokeOnClientMessage} and
+ * {@link InvokeOnServerMessage}.
+ */
+ INVOKE(0),
+
+ /**
+ * Returns the result of an INVOKE, INVOKE_SPECIAL, or LOAD_MODULE message.
+ */
+ RETURN(1),
+
+ /**
+ * v1 LOAD_MODULE message.
+ */
+ OLD_LOAD_MODULE(2),
+
+ /**
+ * Normal closure of the connection.
+ */
+ QUIT(3),
+
+ /**
+ * A request by the server to load JSNI source into the client's JS engine.
+ */
+ LOAD_JSNI(4),
+
+ INVOKE_SPECIAL(5),
+
+ FREE_VALUE(6),
+
+ /**
+ * Abnormal termination of the connection.
+ */
+ FATAL_ERROR(7),
+
+ CHECK_VERSIONS(8),
+
+ PROTOCOL_VERSION(9),
+
+ CHOOSE_TRANSPORT(10),
+
+ SWITCH_TRANSPORT(11),
+
+ LOAD_MODULE(12);
+
+ private final int id;
+
+ private MessageType(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+ }
+
+ /**
+ * Represents an object on the other side of the channel, known to this side
+ * by an reference ID.
+ */
+ public interface RemoteObjectRef {
+
+ /**
+ * Return the reference ID for this object.
+ */
+ int getRefid();
}
/**
@@ -149,10 +209,23 @@
/**
* Enumeration of dispatch IDs on object 0 (the ServerMethods object).
*
+ * <p>Ids are set specifically rather than relying on the ordinal to avoid
+ * synchronization problems with the client.
+ *
* TODO: hasMethod/hasProperty no longer used, remove them!
*/
public enum SpecialDispatchId {
- HasMethod, HasProperty, GetProperty, SetProperty,
+ HasMethod(0), HasProperty(1), GetProperty(2), SetProperty(3);
+
+ private final int id;
+
+ private SpecialDispatchId(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
}
public abstract void freeValue(BrowserChannel channel, int[] ids);
@@ -197,24 +270,28 @@
/**
* Primitive values.
*/
- NULL, BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, STRING,
+ NULL(0), BOOLEAN(1), BYTE(2), CHAR(3), SHORT(4), INT(5), LONG(6),
+ FLOAT(7), DOUBLE(8), STRING(9),
/**
* Representations of Java or JS objects, sent as an index into a table
* kept on the side holding the actual object.
*/
- JAVA_OBJECT, JS_OBJECT,
+ JAVA_OBJECT(10), JS_OBJECT(11),
/**
* A Javascript undef value, also used for void returns.
*/
- UNDEFINED;
+ UNDEFINED(12);
- ValueType() {
+ private final int id;
+
+ private ValueType(int id) {
+ this.id = id;
}
byte getTag() {
- return (byte) this.ordinal();
+ return (byte) id;
}
}
@@ -556,11 +633,11 @@
hostedHtmlVersion);
}
- private final int minVersion;
+ private final String hostedHtmlVersion;
private final int maxVersion;
- private final String hostedHtmlVersion;
+ private final int minVersion;
public CheckVersionsMessage(BrowserChannel channel, int minVersion,
int maxVersion, String hostedHtmlVersion) {
@@ -585,7 +662,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.CHECK_VERSIONS.ordinal());
+ stream.writeByte(MessageType.CHECK_VERSIONS.getId());
stream.writeInt(minVersion);
stream.writeInt(maxVersion);
writeUtf8String(stream, hostedHtmlVersion);
@@ -625,7 +702,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.CHOOSE_TRANSPORT.ordinal());
+ stream.writeByte(MessageType.CHOOSE_TRANSPORT.getId());
stream.writeInt(transports.length);
for (String transport : transports) {
writeUtf8String(stream, transport);
@@ -660,7 +737,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.FATAL_ERROR.ordinal());
+ stream.writeByte(MessageType.FATAL_ERROR.getId());
writeUtf8String(stream, error);
}
}
@@ -686,7 +763,7 @@
public static void send(BrowserChannel channel, int[] ids)
throws IOException {
DataOutputStream stream = channel.getStreamToOtherSide();
- stream.writeByte(MessageType.FREE_VALUE.ordinal());
+ stream.writeByte(MessageType.FREE_VALUE.getId());
stream.writeInt(ids.length);
for (int id : ids) {
stream.writeInt(id);
@@ -730,19 +807,18 @@
DataInputStream stream = channel.getStreamFromOtherSide();
// NOTE: Tag has already been read.
String methodName = readUtf8String(stream);
- Value thisRef = readValue(stream);
+ Value thisRef = channel.readValue(stream);
int argLen = stream.readInt();
Value[] args = new Value[argLen];
for (int i = 0; i < argLen; i++) {
- args[i] = readValue(stream);
+ args[i] = channel.readValue(stream);
}
return new InvokeOnClientMessage(channel, methodName, thisRef, args);
}
- private final String methodName;
-
- private final Value thisRef;
private final Value[] args;
+ private final String methodName;
+ private final Value thisRef;
public InvokeOnClientMessage(BrowserChannel channel, String methodName,
Value thisRef, Value[] args) {
@@ -768,12 +844,12 @@
public void send() throws IOException {
final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.INVOKE.ordinal());
+ stream.writeByte(MessageType.INVOKE.getId());
writeUtf8String(stream, methodName);
- writeValue(stream, thisRef);
+ getBrowserChannel().writeValue(stream, thisRef);
stream.writeInt(args.length);
for (int i = 0; i < args.length; i++) {
- writeValue(stream, args[i]);
+ getBrowserChannel().writeValue(stream, args[i]);
}
stream.flush();
}
@@ -793,19 +869,19 @@
DataInputStream stream = channel.getStreamFromOtherSide();
// NOTE: Tag has already been read.
int methodDispatchId = stream.readInt();
- Value thisRef = readValue(stream);
+ Value thisRef = channel.readValue(stream);
int argLen = stream.readInt();
Value[] args = new Value[argLen];
for (int i = 0; i < argLen; i++) {
- args[i] = readValue(stream);
+ args[i] = channel.readValue(stream);
}
return new InvokeOnServerMessage(channel, methodDispatchId, thisRef,
args);
}
+ private final Value[] args;
private final int methodDispatchId;
private final Value thisRef;
- private final Value[] args;
public InvokeOnServerMessage(BrowserChannel channel, int methodDispatchId,
Value thisRef, Value[] args) {
@@ -831,12 +907,12 @@
public void send() throws IOException {
final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.INVOKE.ordinal());
+ stream.writeByte(MessageType.INVOKE.getId());
stream.writeInt(methodDispatchId);
- writeValue(stream, thisRef);
+ getBrowserChannel().writeValue(stream, thisRef);
stream.writeInt(args.length);
for (int i = 0; i < args.length; i++) {
- writeValue(stream, args[i]);
+ getBrowserChannel().writeValue(stream, args[i]);
}
stream.flush();
}
@@ -860,13 +936,13 @@
final int argLen = stream.readInt();
final Value[] args = new Value[argLen];
for (int i = 0; i < argLen; i++) {
- args[i] = readValue(stream);
+ args[i] = channel.readValue(stream);
}
return new InvokeSpecialMessage(channel, dispatchId, args);
}
- private final SpecialDispatchId dispatchId;
private final Value[] args;
+ private final SpecialDispatchId dispatchId;
public InvokeSpecialMessage(BrowserChannel channel,
SpecialDispatchId dispatchId, Value[] args) {
@@ -887,11 +963,11 @@
public void send() throws IOException {
final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.INVOKE_SPECIAL.ordinal());
- stream.writeByte(dispatchId.ordinal());
+ stream.writeByte(MessageType.INVOKE_SPECIAL.getId());
+ stream.writeByte(dispatchId.getId());
stream.writeInt(args.length);
for (int i = 0; i < args.length; i++) {
- writeValue(stream, args[i]);
+ getBrowserChannel().writeValue(stream, args[i]);
}
stream.flush();
}
@@ -913,7 +989,7 @@
public static void send(BrowserChannel channel, String js)
throws IOException {
DataOutputStream stream = channel.getStreamToOtherSide();
- stream.write(MessageType.LOAD_JSNI.ordinal());
+ stream.write(MessageType.LOAD_JSNI.getId());
writeUtf8String(stream, js);
stream.flush();
}
@@ -959,13 +1035,13 @@
private final String moduleName;
- private final String userAgent;
-
- private final String url;
-
private final String sessionKey;
private final String tabKey;
+
+ private final String url;
+
+ private final String userAgent;
/**
* Creates a LoadModule message to be sent to the server.
@@ -1017,7 +1093,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.LOAD_MODULE.ordinal());
+ stream.writeByte(MessageType.LOAD_MODULE.getId());
writeUtf8String(stream, url);
writeUtf8String(stream, tabKey);
writeUtf8String(stream, sessionKey);
@@ -1069,6 +1145,20 @@
}
/**
+ * Provides a way of allocating JS and Java object ids without knowing
+ * which one is the remote type, so code can be shared between client and
+ * server.
+ */
+ protected interface ObjectRefFactory {
+
+ JavaObjectRef getJavaObjectRef(int refId);
+
+ JsObjectRef getJsObjectRef(int refId);
+
+ Set<Integer> getRefIdsForCleanup();
+ }
+
+ /**
* A request from the client that the server load and initialize a given
* module (original v1 version).
*/
@@ -1085,9 +1175,9 @@
private final String moduleName;
- private final String userAgent;
-
private final int protoVersion;
+
+ private final String userAgent;
public OldLoadModuleMessage(BrowserChannel channel, int protoVersion,
String moduleName, String userAgent) {
@@ -1112,7 +1202,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.OLD_LOAD_MODULE.ordinal());
+ stream.writeByte(MessageType.OLD_LOAD_MODULE.getId());
stream.writeInt(protoVersion);
writeUtf8String(stream, moduleName);
writeUtf8String(stream, userAgent);
@@ -1146,7 +1236,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.PROTOCOL_VERSION.ordinal());
+ stream.writeByte(MessageType.PROTOCOL_VERSION.getId());
stream.writeInt(protocolVersion);
stream.flush();
}
@@ -1162,7 +1252,7 @@
public static void send(BrowserChannel channel) throws IOException {
final DataOutputStream stream = channel.getStreamToOtherSide();
- stream.writeByte(MessageType.QUIT.ordinal());
+ stream.writeByte(MessageType.QUIT.getId());
stream.flush();
}
@@ -1184,16 +1274,16 @@
throws IOException {
final DataInputStream stream = channel.getStreamFromOtherSide();
final boolean isException = stream.readBoolean();
- final Value returnValue = readValue(stream);
+ final Value returnValue = channel.readValue(stream);
return new ReturnMessage(channel, isException, returnValue);
}
public static void send(BrowserChannel channel, boolean isException,
Value returnValue) throws IOException {
final DataOutputStream stream = channel.getStreamToOtherSide();
- stream.writeByte(MessageType.RETURN.ordinal());
+ stream.writeByte(MessageType.RETURN.getId());
stream.writeBoolean(isException);
- writeValue(stream, returnValue);
+ channel.writeValue(stream, returnValue);
stream.flush();
}
@@ -1203,8 +1293,8 @@
returnOrException.getReturnValue());
}
- private final Value returnValue;
private final boolean isException;
+ private final Value returnValue;
public ReturnMessage(BrowserChannel channel, boolean isException,
Value returnValue) {
@@ -1270,7 +1360,7 @@
@Override
public void send() throws IOException {
DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.SWITCH_TRANSPORT.ordinal());
+ stream.writeByte(MessageType.SWITCH_TRANSPORT.getId());
writeUtf8String(stream, transport);
writeUtf8String(stream, transportArgs);
}
@@ -1282,61 +1372,8 @@
public static final int SPECIAL_SERVERMETHODS_OBJECT = 0;
- /**
- * This accumulates JsObjectRefs that are no longer referenced in the JVM.
- */
- private static final ThreadLocal<ReferenceQueue<JsObjectRef>> JSOBJECT_REF_QUEUE = new ThreadLocal<ReferenceQueue<JsObjectRef>>() {
- @Override
- protected ReferenceQueue<JsObjectRef> initialValue() {
- return new ReferenceQueue<JsObjectRef>();
- }
- };
-
- /**
- * This map associates a JS reference id with a Reference to the JSObjectRef
- * that currently represents that id.
- */
- private static final ThreadLocal<Map<Integer, Reference<JsObjectRef>>> JSOBJECT_ID_MAP = new ThreadLocal<Map<Integer, Reference<JsObjectRef>>>() {
- @Override
- protected Map<Integer, Reference<JsObjectRef>> initialValue() {
- return new TreeMap<Integer, Reference<JsObjectRef>>();
- }
- };
-
- /**
- * This maps References to JsObjectRefs back to the original refId. Because we
- * need the refId of the JsValueRef after it's been garbage-collected, this
- * state must be stored externally.
- */
- private static final ThreadLocal<Map<Reference<JsObjectRef>, Integer>> REFERENCE_ID_MAP = new ThreadLocal<Map<Reference<JsObjectRef>, Integer>>() {
- @Override
- protected Map<Reference<JsObjectRef>, Integer> initialValue() {
- return new IdentityHashMap<Reference<JsObjectRef>, Integer>();
- }
- };
-
- /**
- * Obtain the JsObjectRef that is currently in use to act as a proxy for the
- * given JS object id.
- */
- protected static JsObjectRef getJsObjectRef(int refId) {
- // Access is implicitly synchronous due to ThreadLocal
- Map<Integer, Reference<JsObjectRef>> map = JSOBJECT_ID_MAP.get();
- if (map.containsKey(refId)) {
- Reference<JsObjectRef> ref = map.get(refId);
- JsObjectRef toReturn = ref.get();
- if (toReturn != null) {
- return toReturn;
- }
- }
-
- JsObjectRef.checkIdMap(refId);
- JsObjectRef toReturn = new JsObjectRef(refId);
- Reference<JsObjectRef> ref = new WeakReference<JsObjectRef>(toReturn,
- JSOBJECT_REF_QUEUE.get());
- map.put(refId, ref);
- REFERENCE_ID_MAP.get().put(ref, refId);
- return toReturn;
+ protected static JavaObjectRef getJavaObjectRef(int refId) {
+ return new JavaObjectRef(refId);
}
protected static String readUtf8String(DataInputStream stream)
@@ -1347,60 +1384,6 @@
return new String(data, "UTF8");
}
- protected static Value readValue(DataInputStream stream) throws IOException {
- ValueType tag;
- try {
- tag = readValueType(stream);
- } catch (BrowserChannelException e) {
- IOException ee = new IOException();
- ee.initCause(e);
- throw ee;
- }
- Value value = new Value();
- switch (tag) {
- case NULL:
- value.setNull();
- break;
- case UNDEFINED:
- value.setUndefined();
- break;
- case BOOLEAN:
- value.setBoolean(stream.readByte() != 0);
- break;
- case BYTE:
- value.setByte(stream.readByte());
- break;
- case CHAR:
- value.setChar(stream.readChar());
- break;
- case FLOAT:
- value.setFloat(stream.readFloat());
- break;
- case INT:
- value.setInt(stream.readInt());
- break;
- case LONG:
- value.setLong(stream.readLong());
- break;
- case DOUBLE:
- value.setDouble(stream.readDouble());
- break;
- case SHORT:
- value.setShort(stream.readShort());
- break;
- case STRING:
- value.setString(readUtf8String(stream));
- break;
- case JS_OBJECT:
- value.setJsObject(getJsObjectRef(stream.readInt()));
- break;
- case JAVA_OBJECT:
- value.setJavaObject(new JavaObjectRef(stream.readInt()));
- break;
- }
- return value;
- }
-
protected static ValueType readValueType(DataInputStream stream)
throws IOException, BrowserChannelException {
int type = stream.readByte();
@@ -1487,7 +1470,186 @@
}
}
- protected static void writeValue(DataOutputStream stream, Value value)
+ private static void writeUndefined(DataOutputStream stream)
+ throws IOException {
+ stream.writeByte(ValueType.UNDEFINED.getTag());
+ }
+
+ private final ObjectRefFactory objectRefFactory;
+
+ private Socket socket;
+
+ private final DataInputStream streamFromOtherSide;
+
+ private final DataOutputStream streamToOtherSide;
+
+ public BrowserChannel(Socket socket, ObjectRefFactory objectRefFactory)
+ throws IOException {
+ this(new BufferedInputStream(socket.getInputStream()),
+ new BufferedOutputStream(socket.getOutputStream()),
+ objectRefFactory);
+ this.socket = socket;
+ }
+
+ protected BrowserChannel(InputStream inputStream, OutputStream outputStream,
+ ObjectRefFactory objectRefFactory)
+ throws IOException {
+ streamFromOtherSide = new DataInputStream(inputStream);
+ streamToOtherSide = new DataOutputStream(outputStream);
+ socket = null;
+ this.objectRefFactory = objectRefFactory;
+ }
+
+ public void endSession() {
+ Utility.close(streamFromOtherSide);
+ Utility.close(streamToOtherSide);
+ Utility.close(socket);
+ }
+
+ /**
+ * @return a set of remote object reference IDs to be freed.
+ */
+ public Set<Integer> getRefIdsForCleanup() {
+ return objectRefFactory.getRefIdsForCleanup();
+ }
+
+ public String getRemoteEndpoint() {
+ if (socket == null) {
+ return "";
+ }
+ return socket.getInetAddress().getCanonicalHostName() + ":"
+ + socket.getPort();
+ }
+
+ public Value invoke(String methodName, Value vthis, Value[] vargs,
+ SessionHandler handler) throws IOException, BrowserChannelException {
+ new InvokeOnClientMessage(this, methodName, vthis, vargs).send();
+ final ReturnMessage msg = reactToMessagesWhileWaitingForReturn(handler);
+ return msg.returnValue;
+ }
+
+ public void reactToMessages(SessionHandler handler) throws IOException,
+ BrowserChannelException {
+ do {
+ getStreamToOtherSide().flush();
+ MessageType messageType = Message.readMessageType(getStreamFromOtherSide());
+ switch (messageType) {
+ case FREE_VALUE:
+ final FreeMessage freeMsg = FreeMessage.receive(this);
+ handler.freeValue(this, freeMsg.getIds());
+ break;
+ case INVOKE:
+ final InvokeOnServerMessage imsg = InvokeOnServerMessage.receive(this);
+ ExceptionOrReturnValue result = handler.invoke(this, imsg.getThis(),
+ imsg.getMethodDispatchId(), imsg.getArgs());
+ sendFreedValues();
+ ReturnMessage.send(this, result);
+ break;
+ case INVOKE_SPECIAL:
+ handleInvokeSpecial(handler);
+ break;
+ case QUIT:
+ return;
+ default:
+ throw new BrowserChannelException("Invalid message type "
+ + messageType);
+ }
+ } while (true);
+ }
+
+ public ReturnMessage reactToMessagesWhileWaitingForReturn(
+ SessionHandler handler) throws IOException, BrowserChannelException {
+ do {
+ getStreamToOtherSide().flush();
+ MessageType messageType = Message.readMessageType(getStreamFromOtherSide());
+ switch (messageType) {
+ case FREE_VALUE:
+ final FreeMessage freeMsg = FreeMessage.receive(this);
+ handler.freeValue(this, freeMsg.getIds());
+ break;
+ case RETURN:
+ return ReturnMessage.receive(this);
+ case INVOKE:
+ final InvokeOnServerMessage imsg = InvokeOnServerMessage.receive(this);
+ ExceptionOrReturnValue result = handler.invoke(this, imsg.getThis(),
+ imsg.getMethodDispatchId(), imsg.getArgs());
+ sendFreedValues();
+ ReturnMessage.send(this, result);
+ break;
+ case INVOKE_SPECIAL:
+ handleInvokeSpecial(handler);
+ break;
+ default:
+ throw new BrowserChannelException("Invalid message type "
+ + messageType + " received waiting for return.");
+ }
+ } while (true);
+ }
+
+ protected DataInputStream getStreamFromOtherSide() {
+ return streamFromOtherSide;
+ }
+
+ protected DataOutputStream getStreamToOtherSide() {
+ return streamToOtherSide;
+ }
+
+ protected Value readValue(DataInputStream stream) throws IOException {
+ ValueType tag;
+ try {
+ tag = readValueType(stream);
+ } catch (BrowserChannelException e) {
+ IOException ee = new IOException();
+ ee.initCause(e);
+ throw ee;
+ }
+ Value value = new Value();
+ switch (tag) {
+ case NULL:
+ value.setNull();
+ break;
+ case UNDEFINED:
+ value.setUndefined();
+ break;
+ case BOOLEAN:
+ value.setBoolean(stream.readByte() != 0);
+ break;
+ case BYTE:
+ value.setByte(stream.readByte());
+ break;
+ case CHAR:
+ value.setChar(stream.readChar());
+ break;
+ case FLOAT:
+ value.setFloat(stream.readFloat());
+ break;
+ case INT:
+ value.setInt(stream.readInt());
+ break;
+ case LONG:
+ value.setLong(stream.readLong());
+ break;
+ case DOUBLE:
+ value.setDouble(stream.readDouble());
+ break;
+ case SHORT:
+ value.setShort(stream.readShort());
+ break;
+ case STRING:
+ value.setString(readUtf8String(stream));
+ break;
+ case JS_OBJECT:
+ value.setJsObject(objectRefFactory.getJsObjectRef(stream.readInt()));
+ break;
+ case JAVA_OBJECT:
+ value.setJavaObject(objectRefFactory.getJavaObjectRef(
+ stream.readInt()));
+ break;
+ }
+ return value;
+ }
+
+ protected void writeValue(DataOutputStream stream, Value value)
throws IOException {
if (value.isNull()) {
writeNull(stream);
@@ -1518,147 +1680,6 @@
}
}
- private static void writeUndefined(DataOutputStream stream)
- throws IOException {
- stream.writeByte(ValueType.UNDEFINED.getTag());
- }
-
- private final DataInputStream streamFromOtherSide;
-
- private final DataOutputStream streamToOtherSide;
-
- private Socket socket;
-
- public BrowserChannel(Socket socket) throws IOException {
- this(new BufferedInputStream(socket.getInputStream()),
- new BufferedOutputStream(socket.getOutputStream()));
- this.socket = socket;
- }
-
- protected BrowserChannel(InputStream inputStream, OutputStream outputStream)
- throws IOException {
- streamFromOtherSide = new DataInputStream(inputStream);
- streamToOtherSide = new DataOutputStream(outputStream);
- socket = null;
- }
-
- public void endSession() {
- Utility.close(streamFromOtherSide);
- Utility.close(streamToOtherSide);
- Utility.close(socket);
- }
-
- public Set<Integer> getRefIdsForCleanup() {
- // Access to these objects is inherently synchronous
- Map<Integer, Reference<JsObjectRef>> objectMap = JSOBJECT_ID_MAP.get();
- Map<Reference<JsObjectRef>, Integer> refIdMap = REFERENCE_ID_MAP.get();
- ReferenceQueue<JsObjectRef> q = JSOBJECT_REF_QUEUE.get();
- Set<Integer> toReturn = new HashSet<Integer>();
-
- // Find all refIds associated with previous garbage collection cycles
- Reference<? extends JsObjectRef> ref;
- while ((ref = q.poll()) != null) {
- Integer i = refIdMap.remove(ref);
- assert i != null;
- toReturn.add(i);
- }
-
- /*
- * Check for liveness. This is necessary because the last reference to a
- * JsObjectRef could have been cleared and a new reference to that refId
- * created before this method has been called.
- */
- for (Iterator<Integer> i = toReturn.iterator(); i.hasNext();) {
- Integer refId = i.next();
- if (objectMap.containsKey(refId)) {
- if (objectMap.get(refId).get() != null) {
- i.remove();
- } else {
- objectMap.remove(refId);
- }
- }
- }
-
- return toReturn;
- }
-
- public String getRemoteEndpoint() {
- if (socket == null) {
- return "";
- }
- return socket.getInetAddress().getCanonicalHostName() + ":"
- + socket.getPort();
- }
-
- public Value invoke(String methodName, Value vthis, Value[] vargs,
- SessionHandler handler) throws IOException, BrowserChannelException {
- new InvokeOnClientMessage(this, methodName, vthis, vargs).send();
- final ReturnMessage msg = reactToMessagesWhileWaitingForReturn(handler);
- return msg.returnValue;
- }
-
- public void reactToMessages(SessionHandler handler) throws IOException,
- BrowserChannelException {
- do {
- getStreamToOtherSide().flush();
- MessageType messageType = Message.readMessageType(getStreamFromOtherSide());
- switch (messageType) {
- case FREE_VALUE:
- final FreeMessage freeMsg = FreeMessage.receive(this);
- handler.freeValue(this, freeMsg.getIds());
- break;
- case INVOKE:
- final InvokeOnServerMessage imsg = InvokeOnServerMessage.receive(this);
- ReturnMessage.send(this, handler.invoke(this, imsg.getThis(),
- imsg.getMethodDispatchId(), imsg.getArgs()));
- break;
- case INVOKE_SPECIAL:
- handleInvokeSpecial(handler);
- break;
- case QUIT:
- return;
- default:
- throw new BrowserChannelException("Invalid message type "
- + messageType);
- }
- } while (true);
- }
-
- public ReturnMessage reactToMessagesWhileWaitingForReturn(
- SessionHandler handler) throws IOException, BrowserChannelException {
- do {
- getStreamToOtherSide().flush();
- MessageType messageType = Message.readMessageType(getStreamFromOtherSide());
- switch (messageType) {
- case FREE_VALUE:
- final FreeMessage freeMsg = FreeMessage.receive(this);
- handler.freeValue(this, freeMsg.getIds());
- break;
- case RETURN:
- return ReturnMessage.receive(this);
- case INVOKE:
- final InvokeOnServerMessage imsg = InvokeOnServerMessage.receive(this);
- ReturnMessage.send(this, handler.invoke(this, imsg.getThis(),
- imsg.getMethodDispatchId(), imsg.getArgs()));
- break;
- case INVOKE_SPECIAL:
- handleInvokeSpecial(handler);
- break;
- default:
- throw new BrowserChannelException("Invalid message type "
- + messageType + " received waiting for return.");
- }
- } while (true);
- }
-
- protected DataInputStream getStreamFromOtherSide() {
- return streamFromOtherSide;
- }
-
- protected DataOutputStream getStreamToOtherSide() {
- return streamToOtherSide;
- }
-
private void handleInvokeSpecial(SessionHandler handler) throws IOException,
BrowserChannelException {
final InvokeSpecialMessage ismsg = InvokeSpecialMessage.receive(this);
@@ -1680,4 +1701,17 @@
}
ReturnMessage.send(this, retExc);
}
+
+ private void sendFreedValues() throws IOException {
+ Set<Integer> freed = objectRefFactory.getRefIdsForCleanup();
+ int n = freed.size();
+ if (n > 0) {
+ int[] ids = new int[n];
+ int i = 0;
+ for (Integer id : freed) {
+ ids[i++] = id;
+ }
+ FreeMessage.send(this, ids);
+ }
+ }
}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelClient.java b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelClient.java
index 4713e95..c268194 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelClient.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelClient.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.net.Socket;
+import java.util.Set;
/**
* Implementation of the BrowserChannel for the client side.
@@ -28,7 +29,32 @@
*/
public class BrowserChannelClient extends BrowserChannel {
- private static final int PROTOCOL_VERSION = 2;
+ private static class ClientObjectRefFactory implements ObjectRefFactory {
+
+ private final RemoteObjectTable<JavaObjectRef> remoteObjectTable;
+
+ public ClientObjectRefFactory() {
+ remoteObjectTable = new RemoteObjectTable<JavaObjectRef>();
+ }
+
+ public JavaObjectRef getJavaObjectRef(int refId) {
+ JavaObjectRef objectRef = remoteObjectTable.getRemoteObjectRef(refId);
+ if (objectRef == null) {
+ objectRef = new JavaObjectRef(refId);
+ remoteObjectTable.putRemoteObjectRef(refId, objectRef);
+ }
+ return objectRef;
+ }
+
+ public JsObjectRef getJsObjectRef(int refId) {
+ return new JsObjectRef(refId);
+ }
+
+ public Set<Integer> getRefIdsForCleanup() {
+ return remoteObjectTable.getRefIdsForCleanup();
+ }
+ }
+
private final HtmlUnitSessionHandler htmlUnitSessionHandler;
private final PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
private final String moduleName;
@@ -41,7 +67,8 @@
public BrowserChannelClient(String addressParts[], String url,
String sessionKey, String moduleName, String versionString,
HtmlUnitSessionHandler htmlUnitSessionHandler) throws IOException {
- super(new Socket(addressParts[0], Integer.parseInt(addressParts[1])));
+ super(new Socket(addressParts[0], Integer.parseInt(addressParts[1])),
+ new ClientObjectRefFactory());
connected = true;
this.url = url;
this.sessionKey = sessionKey;
@@ -108,8 +135,8 @@
private boolean init() throws IOException, BrowserChannelException {
logger.log(TreeLogger.DEBUG, "sending " + MessageType.CHECK_VERSIONS
+ " message");
- new CheckVersionsMessage(this, PROTOCOL_VERSION, PROTOCOL_VERSION,
- versionString).send();
+ new CheckVersionsMessage(this, BROWSERCHANNEL_PROTOCOL_VERSION,
+ BROWSERCHANNEL_PROTOCOL_VERSION, versionString).send();
MessageType type = Message.readMessageType(getStreamFromOtherSide());
switch (type) {
case PROTOCOL_VERSION:
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
index 9ec6599..57ee158 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.net.Socket;
+import java.util.Set;
/**
*
@@ -27,11 +28,37 @@
public final class BrowserChannelServer extends BrowserChannel
implements Runnable {
+ private static class ServerObjectRefFactory implements ObjectRefFactory {
+
+ private final RemoteObjectTable<JsObjectRef> remoteObjectTable;
+
+ public ServerObjectRefFactory() {
+ remoteObjectTable = new RemoteObjectTable<JsObjectRef>();
+ }
+
+ public JavaObjectRef getJavaObjectRef(int refId) {
+ return new JavaObjectRef(refId);
+ }
+
+ public JsObjectRef getJsObjectRef(int refId) {
+ JsObjectRef objectRef = remoteObjectTable.getRemoteObjectRef(refId);
+ if (objectRef == null) {
+ objectRef = new JsObjectRef(refId);
+ remoteObjectTable.putRemoteObjectRef(refId, objectRef);
+ }
+ return objectRef;
+ }
+
+ public Set<Integer> getRefIdsForCleanup() {
+ return remoteObjectTable.getRefIdsForCleanup();
+ }
+ }
+
public static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject";
private SessionHandler handler;
- private final ObjectsTable javaObjectsInBrowser = new ObjectsTable();
+ private final ServerObjectsTable javaObjectsInBrowser = new ServerObjectsTable();
private TreeLogger logger;
@@ -43,7 +70,7 @@
public BrowserChannelServer(TreeLogger initialLogger, Socket socket,
SessionHandler handler) throws IOException {
- super(socket);
+ super(socket, new ServerObjectRefFactory());
this.handler = handler;
this.logger = initialLogger;
Thread thread = new Thread(this);
@@ -62,7 +89,7 @@
}
}
- public ObjectsTable getJavaObjectsExposedInBrowser() {
+ public ServerObjectsTable getJavaObjectsExposedInBrowser() {
return javaObjectsInBrowser;
}
@@ -84,7 +111,7 @@
public void invokeJavascript(CompilingClassLoader ccl, JsValueOOPHM jsthis,
String methodName, JsValueOOPHM[] args, JsValueOOPHM returnJsValue)
throws Throwable {
- final ObjectsTable remoteObjects = getJavaObjectsExposedInBrowser();
+ final ServerObjectsTable remoteObjects = getJavaObjectsExposedInBrowser();
Value vthis = convertFromJsValue(remoteObjects, jsthis);
Value[] vargs = new Value[args.length];
for (int i = 0; i < args.length; ++i) {
@@ -175,7 +202,7 @@
* @param jsval value to convert
* @return jsval as a Value object.
*/
- Value convertFromJsValue(ObjectsTable localObjects, JsValueOOPHM jsval) {
+ Value convertFromJsValue(ServerObjectsTable localObjects, JsValueOOPHM jsval) {
Value value = new Value();
if (jsval.isNull()) {
value.setNull();
@@ -214,7 +241,7 @@
* @param val Value to convert
* @param jsval JsValue object to receive converted value.
*/
- void convertToJsValue(CompilingClassLoader ccl, ObjectsTable localObjects,
+ void convertToJsValue(CompilingClassLoader ccl, ServerObjectsTable localObjects,
Value val, JsValueOOPHM jsval) {
switch (val.getType()) {
case NULL:
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/JsValueOOPHM.java b/dev/oophm/src/com/google/gwt/dev/shell/JsValueOOPHM.java
index 0e3d7c5..4ab0c77 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/JsValueOOPHM.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/JsValueOOPHM.java
@@ -144,7 +144,6 @@
* @param jsRefId pointer to underlying JsRootedValue as an integer.
*/
public JsValueOOPHM(int jsRefId) {
- JsObjectRef.checkIdMap(jsRefId);
this.value = new JsObjectRef(jsRefId);
}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
index 1eeae51..345580e 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
@@ -48,7 +48,7 @@
@Override
public void freeValue(BrowserChannel channel, int[] ids) {
BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
- ObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
+ ServerObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
for (int id : ids) {
localObjects.free(id);
}
@@ -60,7 +60,7 @@
BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
ModuleSpace moduleSpace = moduleMap.get(serverChannel);
assert moduleSpace != null;
- ObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
+ ServerObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
try {
JsValueOOPHM obj = new JsValueOOPHM();
DispatchObject dispObj;
@@ -91,7 +91,7 @@
public ExceptionOrReturnValue invoke(BrowserChannel channel, Value thisVal,
int methodDispatchId, Value[] args) {
BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
- ObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
+ ServerObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
ModuleSpace moduleSpace = moduleMap.get(serverChannel);
assert moduleSpace != null;
CompilingClassLoader cl = moduleSpace.getIsolatedClassLoader();
@@ -190,7 +190,7 @@
BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
ModuleSpace moduleSpace = moduleMap.get(serverChannel);
assert moduleSpace != null;
- ObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
+ ServerObjectsTable localObjects = serverChannel.getJavaObjectsExposedInBrowser();
try {
JsValueOOPHM obj = new JsValueOOPHM();
DispatchObject dispObj;
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/RemoteObjectTable.java b/dev/oophm/src/com/google/gwt/dev/shell/RemoteObjectTable.java
new file mode 100644
index 0000000..f6004a8
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/shell/RemoteObjectTable.java
@@ -0,0 +1,142 @@
+/*
+ * 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.google.gwt.dev.shell;
+
+import com.google.gwt.dev.shell.BrowserChannel.RemoteObjectRef;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Keeps track of references to remote objects. When the objects are no longer
+ * needed, their ids are returned in {@link #getRefIdsForCleanup()}.
+ *
+ * @param <T> subtype of RemoteObjectRef contained in this table
+ */
+public class RemoteObjectTable<T extends RemoteObjectRef> {
+
+ /**
+ * This maps References to RemoteObjectRefs back to the original refId.
+ * Because we need the refId of the RemoteObjectRef after it's been
+ * garbage-collected, this state must be stored externally.
+ */
+ private final Map<Reference<T>, Integer> idFromRemoteObject;
+
+ /**
+ * This accumulates remote objects that are no longer referenced on this side
+ * of the channel.
+ */
+ private final ReferenceQueue<T> refQueue;
+
+ /**
+ * This map associates a remote object ID with a Reference to the
+ * RemoteObjectRef that currently represents that id.
+ */
+ private final Map<Integer, Reference<T>> remoteObjectFromId;
+
+ /**
+ * Create a new RemoteObjectTable.
+ */
+ public RemoteObjectTable() {
+ refQueue = new ReferenceQueue<T>();
+ remoteObjectFromId = new HashMap<Integer, Reference<T>>();
+ idFromRemoteObject = new IdentityHashMap<Reference<T>, Integer>();
+ }
+
+ /**
+ * @return the set of remote object reference IDs that should be freed.
+ */
+ public synchronized Set<Integer> getRefIdsForCleanup() {
+ // Access to these objects is inherently synchronous
+ Map<Integer, Reference<T>> objectMap = remoteObjectFromId;
+ Map<Reference<T>, Integer> refIdMap = idFromRemoteObject;
+ Set<Integer> toReturn = new HashSet<Integer>();
+
+ // Find all refIds associated with previous garbage collection cycles
+ Reference<? extends RemoteObjectRef> ref;
+ while ((ref = refQueue.poll()) != null) {
+ Integer i = refIdMap.remove(ref);
+ assert i != null;
+ toReturn.add(i);
+ }
+
+ /*
+ * Check for liveness. This is necessary because the last reference to a
+ * RemoteObjectRef could have been cleared and a new reference to that refId
+ * created before this method has been called.
+ */
+ for (Iterator<Integer> i = toReturn.iterator(); i.hasNext();) {
+ Integer refId = i.next();
+ if (objectMap.containsKey(refId)) {
+ if (objectMap.get(refId).get() != null) {
+ i.remove();
+ } else {
+ objectMap.remove(refId);
+ }
+ }
+ }
+
+ return toReturn;
+ }
+
+ /**
+ * Obtain the RemoteObjectRef that is currently in use to act as a proxy for
+ * the given remote object ID.
+ *
+ * @return the RemoteObjectRef or null if the ID is not currently in use
+ */
+ public synchronized T getRemoteObjectRef(int refId) {
+ if (remoteObjectFromId.containsKey(refId)) {
+ Reference<T> ref = remoteObjectFromId.get(refId);
+ T toReturn = ref.get();
+ if (toReturn != null) {
+ return toReturn;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check to see if this ID does not already exist.
+ *
+ * @param refId reference ID to check
+ * @return true if this ID is not currently in use
+ */
+ public synchronized boolean isNewObjectId(int refId) {
+ return !remoteObjectFromId.containsKey(refId)
+ || (remoteObjectFromId.get(refId).get() == null);
+ }
+
+ /**
+ * Store a remote object reference in the table.
+ *
+ * @param refId
+ * @param remoteObjectRef
+ */
+ public synchronized void putRemoteObjectRef(int refId,
+ T remoteObjectRef) {
+ Reference<T> ref = new WeakReference<T>(remoteObjectRef, refQueue);
+ remoteObjectFromId.put(refId, ref);
+ idFromRemoteObject.put(ref, refId);
+ }
+}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/ObjectsTable.java b/dev/oophm/src/com/google/gwt/dev/shell/ServerObjectsTable.java
similarity index 89%
rename from dev/oophm/src/com/google/gwt/dev/shell/ObjectsTable.java
rename to dev/oophm/src/com/google/gwt/dev/shell/ServerObjectsTable.java
index 1d33104..2391dad 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/ObjectsTable.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/ServerObjectsTable.java
@@ -23,7 +23,7 @@
* A class that keeps track of Java objects which have been exposed to the other
* side and assigns unique ids.
*/
-public class ObjectsTable {
+public class ServerObjectsTable {
/**
* A type that records the next-available free id slot to use, without itself
@@ -64,9 +64,10 @@
}
public void free(int id) {
- assert objects.get(id) != null : "Trying to free never-used id " + id;
- assert !(objects.get(id) instanceof Tombstone) : "Duplicate free " + id;
- refMap.remove(objects.get(id));
+ Object object = objects.get(id);
+ assert object != null : "Trying to free never-used id " + id;
+ assert !(object instanceof Tombstone) : "Duplicate free " + id;
+ refMap.remove(object);
objects.put(id, new Tombstone(nextFree));
nextFree = id;
}
diff --git a/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java b/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
index ba7b1d7..d9b0029 100644
--- a/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
+++ b/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
@@ -44,6 +44,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
+import java.util.Set;
/**
* Test for {@link BrowserChannel}.
@@ -55,7 +56,22 @@
private class TestBrowserChannel extends BrowserChannel {
public TestBrowserChannel(InputStream inputStream,
OutputStream outputStream) throws IOException {
- super(inputStream, outputStream);
+ super(inputStream, outputStream, new ObjectRefFactory() {
+ public JavaObjectRef getJavaObjectRef(int refId) {
+ fail("getJavaObjectRef mocked");
+ return null;
+ }
+
+ public JsObjectRef getJsObjectRef(int refId) {
+ fail("getJsObjectRef mocked");
+ return null;
+ }
+
+ public Set<Integer> getRefIdsForCleanup() {
+ fail("getRefIdsForCleanup mocked");
+ return null;
+ }
+ });
}
public MessageType readMessageType() throws IOException,
@@ -80,13 +96,13 @@
public void testBooleanValue() throws IOException {
Value val = new Value();
val.setBoolean(true);
- BrowserChannel.writeValue(oStr, val);
- val = BrowserChannel.readValue(iStr);
+ channel.writeValue(oStr, val);
+ val = channel.readValue(iStr);
assertEquals(ValueType.BOOLEAN, val.getType());
assertEquals(true, val.getBoolean());
val.setBoolean(false);
- BrowserChannel.writeValue(oStr, val);
- val = BrowserChannel.readValue(iStr);
+ channel.writeValue(oStr, val);
+ val = channel.readValue(iStr);
assertEquals(ValueType.BOOLEAN, val.getType());
assertEquals(false, val.getBoolean());
}
diff --git a/dev/oophm/test/com/google/gwt/dev/shell/RemoteObjectTableTest.java b/dev/oophm/test/com/google/gwt/dev/shell/RemoteObjectTableTest.java
new file mode 100644
index 0000000..0103a01
--- /dev/null
+++ b/dev/oophm/test/com/google/gwt/dev/shell/RemoteObjectTableTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.google.gwt.dev.shell;
+
+import com.google.gwt.dev.shell.BrowserChannel.RemoteObjectRef;
+
+import junit.framework.TestCase;
+
+import java.util.Set;
+
+/**
+ * Test RemoteObjectTable.
+ */
+public class RemoteObjectTableTest extends TestCase {
+
+ /**
+ * Mock implementation of a RemoteObjectRef.
+ */
+ public class MockRemoteObjectRef implements RemoteObjectRef {
+
+ public final int refId;
+
+ public MockRemoteObjectRef(int refId) {
+ this.refId = refId;
+ }
+
+ public int getRefid() {
+ return refId;
+ }
+ }
+
+ public void testFree() throws InterruptedException {
+ RemoteObjectTable<MockRemoteObjectRef> table = new RemoteObjectTable<
+ MockRemoteObjectRef>();
+ MockRemoteObjectRef ref = new MockRemoteObjectRef(1);
+ table.putRemoteObjectRef(1, ref);
+ Set<Integer> freed = table.getRefIdsForCleanup();
+ ensureGC();
+ assertEquals(0, freed.size());
+ ref = null;
+ ensureGC();
+ freed = table.getRefIdsForCleanup();
+ assertEquals(1, freed.size());
+ }
+
+ /**
+ * This method attempts to ensure that a GC has happened and any weak
+ * references have been cleaned up.
+ *
+ * @throws InterruptedException
+ */
+ private void ensureGC() throws InterruptedException {
+ // TODO(jat): is this sufficient across all VMs?
+ System.gc();
+ Thread.sleep(10);
+ System.gc();
+ }
+}
diff --git a/user/src/com/google/gwt/junit/RunStyle.java b/user/src/com/google/gwt/junit/RunStyle.java
index 95fa4f2..d054429 100644
--- a/user/src/com/google/gwt/junit/RunStyle.java
+++ b/user/src/com/google/gwt/junit/RunStyle.java
@@ -33,10 +33,7 @@
* the same signature since this will be how the RunStyle is created via
* reflection.
*
- * @param logger TreeLogger instance
* @param shell the containing shell
- * @param args arguments (after the colon in the argument to -runStyle)
- * may be null if no argument is supplied
*/
public RunStyle(JUnitShell shell) {
this.shell = shell;