Add a tabKey to the LoadModule message so that we can differentiate between
a reload in the same tab and opening a new tab, if the plugin has that
capability.

Also, rename write* methods for better symmetry and more tests/doc.

Patch by: jat
Review by: amitmanjhi


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5936 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
index 17ae17c..bb8c0d4 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
@@ -229,7 +229,7 @@
           }
         } catch (e) {
         }
-      }      
+      }
       loadIframe("http://google-web-toolkit.googlecode.com/svn/trunk/plugins/MissingBrowserPlugin.html");
     } else {
       if (!plugin.connect(url, topWin.__gwt_SessionID, $hosted, $moduleName,
diff --git a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
index 7542206..1633bfe 100644
--- a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
@@ -76,8 +76,9 @@
     }
 
     public ModuleSpaceHost createModuleSpaceHost(TreeLogger logger,
-        String moduleName, String userAgent, String url, String sessionKey,
-        String remoteEndpoint) throws UnableToCompleteException {
+        String moduleName, String userAgent, String url, String tabKey,
+        String sessionKey, String remoteEndpoint)
+        throws UnableToCompleteException {
       throw new UnsupportedOperationException();
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java b/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
index 019f268..41e7a27 100644
--- a/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
@@ -53,12 +53,14 @@
    * @param logger
    * @param moduleName
    * @param userAgent
-   * @param url URL of top-level window (may be null for old browser plugins) 
-   * @param sessionKey unique session key (may be null for old browser plugins)
+   * @param url URL of top-level window, may be null for old browser plugins
+   * @param tabKey opaque key for the tab, may be empty string if the plugin
+   *     can't distinguish tabs or null if using an old browser plugin 
+   * @param sessionKey unique session key, may be null for old browser plugins
    * @param remoteEndpoint
    */
   ModuleSpaceHost createModuleSpaceHost(TreeLogger logger, String moduleName,
-      String userAgent, String url, String sessionKey,
+      String userAgent, String url, String tabKey, String sessionKey,
       String remoteEndpoint) throws UnableToCompleteException;
 
   TreeLogger getLogger();
diff --git a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
index b8fe956..6b40983 100644
--- a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
+++ b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
@@ -142,8 +142,9 @@
     }
 
     public ModuleSpaceHost createModuleSpaceHost(TreeLogger mainLogger,
-        String moduleName, String userAgent, String url, String sessionKey,
-        String remoteSocket) throws UnableToCompleteException {
+        String moduleName, String userAgent, String url, String tabKey,
+        String sessionKey, String remoteSocket)
+        throws UnableToCompleteException {
       TreeLogger logger = mainLogger;
       TreeLogger.Type maxLevel = TreeLogger.INFO;
       if (mainLogger instanceof AbstractTreeLogger) {
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 85bafa4..e76ea89 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
@@ -158,9 +158,22 @@
     public abstract ExceptionOrReturnValue invoke(BrowserChannel channel,
         Value thisObj, int dispId, Value[] args);
 
+    /**
+     * Load a new instance of a module.
+     * 
+     * @param logger
+     * @param channel
+     * @param moduleName
+     * @param userAgent
+     * @param url top-level URL of the main page, null if using an old plugin
+     * @param tabKey opaque key of the tab, may be empty if the plugin can't
+     *     distinguish tabs or null if using an old plugin
+     * @param sessionKey opaque key for this session, null if using an old plugin
+     * @return a TreeLogger to use for the module's logs
+     */
     public abstract TreeLogger loadModule(TreeLogger logger,
         BrowserChannel channel, String moduleName, String userAgent, String url,
-        String sessionKey);
+        String tabKey, String sessionKey);
 
     public abstract ExceptionOrReturnValue setProperty(BrowserChannel channel,
         int refId, int dispId, Value newValue);
@@ -570,7 +583,7 @@
       stream.writeByte(MessageType.CHECK_VERSIONS.ordinal());
       stream.writeInt(minVersion);
       stream.writeInt(maxVersion);
-      writeUntaggedString(stream, hostedHtmlVersion);
+      writeUtf8String(stream, hostedHtmlVersion);
       stream.flush();
     }
   }
@@ -610,7 +623,7 @@
       stream.writeByte(MessageType.CHOOSE_TRANSPORT.ordinal());
       stream.writeInt(transports.length);
       for (String transport : transports) {
-        writeUntaggedString(stream, transport);
+        writeUtf8String(stream, transport);
       }
     }
   }
@@ -643,7 +656,7 @@
     public void send() throws IOException {
       DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
       stream.writeByte(MessageType.FATAL_ERROR.ordinal());
-      writeUntaggedString(stream, error);
+      writeUtf8String(stream, error);
     }
   }
 
@@ -751,7 +764,7 @@
       final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
 
       stream.writeByte(MessageType.INVOKE.ordinal());
-      writeUntaggedString(stream, methodName);
+      writeUtf8String(stream, methodName);
       writeValue(stream, thisRef);
       stream.writeInt(args.length);
       for (int i = 0; i < args.length; i++) {
@@ -896,7 +909,7 @@
         throws IOException {
       DataOutputStream stream = channel.getStreamToOtherSide();
       stream.write(MessageType.LOAD_JSNI.ordinal());
-      writeUntaggedString(stream, js);
+      writeUtf8String(stream, js);
       stream.flush();
     }
 
@@ -931,10 +944,11 @@
         throws IOException {
       DataInputStream stream = channel.getStreamFromOtherSide();
       String url = readUtf8String(stream);
+      String tabKey = readUtf8String(stream);
       String sessionKey = readUtf8String(stream);
       String moduleName = readUtf8String(stream);
       String userAgent = readUtf8String(stream);
-      return new LoadModuleMessage(channel, url, sessionKey, moduleName,
+      return new LoadModuleMessage(channel, url, tabKey, sessionKey, moduleName,
           userAgent);
     }
 
@@ -946,10 +960,30 @@
     
     private final String sessionKey;
 
+    private final String tabKey;
+
+    /**
+     * Creates a LoadModule message to be sent to the server.
+     * 
+     * @param channel BrowserChannel instance
+     * @param url URL of main top-level window - may not be null
+     * @param tabKey opaque key identifying the tab in the browser, or an
+     *     empty string if it cannot be determined - may not be null
+     * @param sessionKey opaque key identifying a particular session (ie,
+     *     group of modules) - may not be null
+     * @param moduleName name of GWT module to load - may not be null
+     * @param userAgent user agent identifier of the browser - may not be null
+     */
     public LoadModuleMessage(BrowserChannel channel, String url,
-        String sessionKey, String moduleName, String userAgent) {
+        String tabKey, String sessionKey, String moduleName, String userAgent) {
       super(channel);
+      assert url != null;
+      assert tabKey != null;
+      assert sessionKey != null;
+      assert moduleName != null;
+      assert userAgent != null;
       this.url = url;
+      this.tabKey = tabKey;
       this.sessionKey = sessionKey;
       this.moduleName = moduleName;
       this.userAgent = userAgent;
@@ -963,6 +997,10 @@
       return sessionKey;
     }
 
+    public String getTabKey() {
+      return tabKey;
+    }
+
     public String getUrl() {
       return url;
     }
@@ -975,10 +1013,11 @@
     public void send() throws IOException {
       DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
       stream.writeByte(MessageType.LOAD_MODULE.ordinal());
-      writeUntaggedString(stream, url);
-      writeUntaggedString(stream, sessionKey);
-      writeUntaggedString(stream, moduleName);
-      writeUntaggedString(stream, userAgent);
+      writeUtf8String(stream, url);
+      writeUtf8String(stream, tabKey);
+      writeUtf8String(stream, sessionKey);
+      writeUtf8String(stream, moduleName);
+      writeUtf8String(stream, userAgent);
       stream.flush();
     }
   }
@@ -1070,8 +1109,8 @@
       DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
       stream.writeByte(MessageType.OLD_LOAD_MODULE.ordinal());
       stream.writeInt(protoVersion);
-      writeUntaggedString(stream, moduleName);
-      writeUntaggedString(stream, userAgent);
+      writeUtf8String(stream, moduleName);
+      writeUtf8String(stream, userAgent);
       stream.flush();
     }
   }
@@ -1227,8 +1266,8 @@
     public void send() throws IOException {
       DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
       stream.writeByte(MessageType.SWITCH_TRANSPORT.ordinal());
-      writeUntaggedString(stream, transport);
-      writeUntaggedString(stream, transportArgs);
+      writeUtf8String(stream, transport);
+      writeUtf8String(stream, transportArgs);
     }
   }
 
@@ -1366,50 +1405,14 @@
     return types[type];
   }
 
-  protected static void writeBoolean(DataOutputStream stream, boolean value)
-      throws IOException {
-    stream.writeByte(ValueType.BOOLEAN.getTag());
-    stream.writeBoolean(value);
-  }
-
-  protected static void writeByte(DataOutputStream stream, byte value)
-      throws IOException {
-    stream.writeByte(ValueType.BYTE.getTag());
-    stream.writeByte(value);
-  }
-
-  protected static void writeChar(DataOutputStream stream, char value)
-      throws IOException {
-    stream.writeByte(ValueType.CHAR.getTag());
-    stream.writeChar(value);
-  }
-
-  protected static void writeDouble(DataOutputStream stream, double value)
-      throws IOException {
-    stream.writeByte(ValueType.DOUBLE.getTag());
-    stream.writeDouble(value);
-  }
-
-  protected static void writeFloat(DataOutputStream stream, float value)
-      throws IOException {
-    stream.writeByte(ValueType.FLOAT.getTag());
-    stream.writeFloat(value);
-  }
-
-  protected static void writeInt(DataOutputStream stream, int value)
-      throws IOException {
-    stream.writeByte(ValueType.INT.getTag());
-    stream.writeInt(value);
-  }
-
   protected static void writeJavaObject(DataOutputStream stream,
       JavaObjectRef value) throws IOException {
     stream.writeByte(ValueType.JAVA_OBJECT.getTag());
     stream.writeInt(value.getRefid());
   }
 
-  protected static void writeJsObject(DataOutputStream stream, JsObjectRef value)
-      throws IOException {
+  protected static void writeJsObject(DataOutputStream stream,
+      JsObjectRef value) throws IOException {
     stream.writeByte(ValueType.JS_OBJECT.getTag());
     stream.writeInt(value.getRefid());
   }
@@ -1418,13 +1421,55 @@
     stream.writeByte(ValueType.NULL.getTag());
   }
 
-  protected static void writeShort(DataOutputStream stream, short value)
+  protected static void writeTaggedBoolean(DataOutputStream stream,
+      boolean value) throws IOException {
+    stream.writeByte(ValueType.BOOLEAN.getTag());
+    stream.writeBoolean(value);
+  }
+
+  protected static void writeTaggedByte(DataOutputStream stream, byte value)
+      throws IOException {
+    stream.writeByte(ValueType.BYTE.getTag());
+    stream.writeByte(value);
+  }
+
+  protected static void writeTaggedChar(DataOutputStream stream, char value)
+      throws IOException {
+    stream.writeByte(ValueType.CHAR.getTag());
+    stream.writeChar(value);
+  }
+
+  protected static void writeTaggedDouble(DataOutputStream stream, double value)
+      throws IOException {
+    stream.writeByte(ValueType.DOUBLE.getTag());
+    stream.writeDouble(value);
+  }
+
+  protected static void writeTaggedFloat(DataOutputStream stream, float value)
+      throws IOException {
+    stream.writeByte(ValueType.FLOAT.getTag());
+    stream.writeFloat(value);
+  }
+
+  protected static void writeTaggedInt(DataOutputStream stream, int value)
+      throws IOException {
+    stream.writeByte(ValueType.INT.getTag());
+    stream.writeInt(value);
+  }
+
+  protected static void writeTaggedShort(DataOutputStream stream, short value)
       throws IOException {
     stream.writeByte(ValueType.SHORT.getTag());
     stream.writeShort(value);
   }
 
-  protected static void writeUntaggedString(DataOutputStream stream, String data)
+  protected static void writeTaggedString(DataOutputStream stream, String data)
+      throws IOException {
+    stream.writeByte(ValueType.STRING.getTag());
+    writeUtf8String(stream, data);
+  }
+
+  protected static void writeUtf8String(DataOutputStream stream, String data)
       throws IOException {
     try {
       final byte[] bytes = data.getBytes("UTF8");
@@ -1436,12 +1481,6 @@
     }
   }
 
-  protected static void writeUtf8String(DataOutputStream stream, String data)
-      throws IOException {
-    stream.writeByte(ValueType.STRING.getTag());
-    writeUntaggedString(stream, data);
-  }
-
   protected static void writeValue(DataOutputStream stream, Value value)
       throws IOException {
     if (value.isNull()) {
@@ -1453,21 +1492,21 @@
     } else if (value.isJavaObject()) {
       writeJavaObject(stream, value.getJavaObject());
     } else if (value.isBoolean()) {
-      writeBoolean(stream, value.getBoolean());
+      writeTaggedBoolean(stream, value.getBoolean());
     } else if (value.isByte()) {
-      writeByte(stream, value.getByte());
+      writeTaggedByte(stream, value.getByte());
     } else if (value.isChar()) {
-      writeChar(stream, value.getChar());
+      writeTaggedChar(stream, value.getChar());
     } else if (value.isShort()) {
-      writeShort(stream, value.getShort());
+      writeTaggedShort(stream, value.getShort());
     } else if (value.isDouble()) {
-      writeDouble(stream, value.getDouble());
+      writeTaggedDouble(stream, value.getDouble());
     } else if (value.isFloat()) {
-      writeFloat(stream, value.getFloat());
+      writeTaggedFloat(stream, value.getFloat());
     } else if (value.isInt()) {
-      writeInt(stream, value.getInt());
+      writeTaggedInt(stream, value.getInt());
     } else if (value.isString()) {
-      writeUtf8String(stream, value.getString());
+      writeTaggedString(stream, value.getString());
     } else {
       assert false;
     }
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 4b98529..dd59992 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -271,6 +271,7 @@
     // TODO(jat): add support for getting the a shim plugin downloading the
     //    real plugin via a GetRealPlugin message before CheckVersions
     String url = null;
+    String tabKey = null;
     String sessionKey = null;
     switch (type) {
       case OLD_LOAD_MODULE:
@@ -327,6 +328,7 @@
         }
         LoadModuleMessage loadModule = LoadModuleMessage.receive(this);
         url = loadModule.getUrl();
+        tabKey = loadModule.getTabKey();
         sessionKey = loadModule.getSessionKey();
         moduleName = loadModule.getModuleName();
         userAgent = loadModule.getUserAgent();
@@ -340,7 +342,7 @@
         "Hosting " + moduleName + " for " + userAgent + " on " + url + " @ "
         + sessionKey);
     logger = handler.loadModule(logger, this, moduleName, userAgent, url,
-        sessionKey);
+        tabKey, sessionKey);
     try {
       // send LoadModule response
       ReturnMessage.send(this, false, new Value());
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 1eff0dc..ff73041 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
@@ -155,13 +155,14 @@
 
   @Override
   public TreeLogger loadModule(TreeLogger logger, BrowserChannel channel,
-      String moduleName, String userAgent, String url, String sessionKey) {
+      String moduleName, String userAgent, String url, String tabKey,
+      String sessionKey) {
     try {
       // Attach a new ModuleSpace to make it programmable.
       //
       BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
       ModuleSpaceHost msh = host.createModuleSpaceHost(logger, moduleName,
-          userAgent, url, sessionKey, channel.getRemoteEndpoint());
+          userAgent, url, tabKey, sessionKey, channel.getRemoteEndpoint());
       this.logger = logger = msh.getLogger();
       ModuleSpace moduleSpace = new ModuleSpaceOOPHM(msh, moduleName,
           serverChannel);
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 9f472f2..ba7b1d7 100644
--- a/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
+++ b/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
@@ -224,17 +224,81 @@
       BrowserChannelException {
     String url = "http://www.google.com";
     String sessionKey = "asdkfjklAI*23ja";
+    String tabKey = "372F4";
     String moduleName = "org.example.Hello";
     String userAgent = "Firefox";
-    new LoadModuleMessage(channel, url, sessionKey, moduleName,
+    new LoadModuleMessage(channel, url, tabKey, sessionKey, moduleName,
         userAgent).send();
     MessageType type = channel.readMessageType();
     assertEquals(MessageType.LOAD_MODULE, type);
     LoadModuleMessage message = LoadModuleMessage.receive(channel);
     assertEquals(url, message.getUrl());
+    assertEquals(tabKey, message.getTabKey());
     assertEquals(sessionKey, message.getSessionKey());
     assertEquals(moduleName, message.getModuleName());
     assertEquals(userAgent, message.getUserAgent());
+    url = "https://www.google.com:8443/extra_stuff_that_is_really_long?foo";
+    sessionKey = "asdfkasdjfkjaskldfjkajsfkjasdfjklaasdkfjklAI*23ja";
+    tabKey = "";
+    moduleName = "showcase";
+    userAgent = "Safari";
+    new LoadModuleMessage(channel, url, tabKey, sessionKey, moduleName,
+        userAgent).send();
+    type = channel.readMessageType();
+    assertEquals(MessageType.LOAD_MODULE, type);
+    message = LoadModuleMessage.receive(channel);
+    assertEquals(url, message.getUrl());
+    assertEquals(tabKey, message.getTabKey());
+    assertEquals(sessionKey, message.getSessionKey());
+    assertEquals(moduleName, message.getModuleName());
+    assertEquals(userAgent, message.getUserAgent());
+    
+    // create a separate channel so we don't cause problems with partial
+    // messages written to the stream
+    TemporaryBufferStream tempBufferStream = new TemporaryBufferStream();
+    TestBrowserChannel trashableChannel = new TestBrowserChannel(
+        tempBufferStream.getInputStream(),
+          tempBufferStream.getOutputStream()); 
+
+    try {
+      new LoadModuleMessage(trashableChannel, null, tabKey, sessionKey, moduleName,
+          userAgent).send();
+      fail("Expected exception with null url");
+    } catch (Exception expected) {
+      // will be AssertionError if assertions are turned on, otherwise NPE
+    }
+    
+    try {
+      new LoadModuleMessage(trashableChannel, url, null, sessionKey, moduleName,
+          userAgent).send();
+      fail("Expected exception with null tabKey");
+    } catch (Exception expected) {
+      // will be AssertionError if assertions are turned on, otherwise NPE
+    }
+    
+    try {
+      new LoadModuleMessage(trashableChannel, url, tabKey, null, moduleName,
+          userAgent).send();
+      fail("Expected exception with null sessionKey");
+    } catch (Exception expected) {
+      // will be AssertionError if assertions are turned on, otherwise NPE
+    }
+    
+    try {
+      new LoadModuleMessage(trashableChannel, url, tabKey, sessionKey, null,
+          userAgent).send();
+      fail("Expected exception with null moduleName");
+    } catch (Exception expected) {
+      // will be AssertionError if assertions are turned on, otherwise NPE
+    }
+    
+    try {
+      new LoadModuleMessage(trashableChannel, url, tabKey, sessionKey, moduleName,
+          null).send();
+      fail("Expected exception with null userAgent");
+    } catch (Exception expected) {
+      // will be AssertionError if assertions are turned on, otherwise NPE
+    }
   }
   
   public void testOldLoadModuleMessage() throws IOException,