Copy long<->base64 code to Base64Utils for server RPC code

Review at http://gwt-code-reviews.appspot.com/639801

Review by: jat@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8274 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/super/com/google/gwt/lang/LongLib.java b/dev/core/super/com/google/gwt/lang/LongLib.java
index edffae2..205f547 100644
--- a/dev/core/super/com/google/gwt/lang/LongLib.java
+++ b/dev/core/super/com/google/gwt/lang/LongLib.java
@@ -31,8 +31,6 @@
   
   private static LongEmul[] boxedValues;
 
-  private static boolean haveNonZero;
-
   public static LongEmul add(LongEmul a, LongEmul b) {
     int sum0 = getL(a) + getL(b);
     int sum1 = getM(a) + getM(b) + (sum0 >> BITS);
@@ -46,61 +44,6 @@
   }
 
   /**
-   * Return an optionally single-quoted string containing a base-64 encoded
-   * version of the given long value.
-   */
-  public static String base64Emit(long value, boolean quote) {
-    // Convert to ints early to avoid need for long ops
-    int low = (int) (value & 0xffffffff);
-    int high = (int) (value >> 32);
-
-    StringBuilder sb = new StringBuilder();
-    if (quote) {
-      sb.append('\'');
-    }
-    haveNonZero = false;
-    base64Append(sb, (high >> 28) & 0xf); // bits 63 - 60
-    base64Append(sb, (high >> 22) & 0x3f); // bits 59 - 54
-    base64Append(sb, (high >> 16) & 0x3f); // bits 53 - 48
-    base64Append(sb, (high >> 10) & 0x3f); // bits 47 - 42
-    base64Append(sb, (high >> 4) & 0x3f); // bits 41 - 36
-    int v = ((high & 0xf) << 2) | ((low >> 30) & 0x3);
-    base64Append(sb, v); // bits 35 - 30
-    base64Append(sb, (low >> 24) & 0x3f); // bits 29 - 24
-    base64Append(sb, (low >> 18) & 0x3f); // bits 23 - 18
-    base64Append(sb, (low >> 12) & 0x3f); // bits 17 - 12
-    base64Append(sb, (low >> 6) & 0x3f); // bits 11 - 6
-    haveNonZero = true; // always emit final digit
-    base64Append(sb, low & 0x3f); // bits 5 - 0
-    if (quote) {
-      sb.append('\'');
-    }
-
-    return sb.toString();
-  }
-
-  /**
-   * Parse a string containing a base-64 encoded version of a long value.
-   */
-  public static long base64Parse(String value) {
-    int pos = 0;
-    char first = value.charAt(pos++);
-    int len = value.length();
-    
-    // Skip surrounding single quotes
-    if (first == '\'') {
-      first = value.charAt(pos++);
-      len--;
-    }
-    long longVal = base64Value(first);
-    while (pos < len) {
-      longVal <<= 6;
-      longVal |= base64Value(value.charAt(pos++));
-    }
-    return longVal;
-  }
-
-  /**
    * Compare the receiver a to the argument b.
    * 
    * @return 0 if they are the same, a positive value if the receiver is
@@ -244,6 +187,22 @@
     }
   }
 
+  /**
+   * Parse a string containing a base-64 encoded version of a long value.
+   * 
+   * Keep this synchronized with the version in Base64Utils.
+   */
+  public static long longFromBase64(String value) {
+    int pos = 0;
+    long longVal = base64Value(value.charAt(pos++));
+    int len = value.length();
+    while (pos < len) {
+      longVal <<= 6;
+      longVal |= base64Value(value.charAt(pos++));
+    }
+    return longVal;
+  }
+
   public static boolean lt(LongEmul a, LongEmul b) {
     return !gte(a, b);
   }
@@ -443,6 +402,34 @@
     return create(sum0 & MASK, sum1 & MASK, sum2 & MASK_2);
   }
 
+  /**
+   * Return an optionally single-quoted string containing a base-64 encoded
+   * version of the given long value.
+   * 
+   * Keep this synchronized with the version in Base64Utils.
+   */
+  public static String toBase64(long value) {
+    // Convert to ints early to avoid need for long ops
+    int low = (int) (value & 0xffffffff);
+    int high = (int) (value >> 32);
+
+    StringBuilder sb = new StringBuilder();
+    boolean haveNonZero = base64Append(sb, (high >> 28) & 0xf, false);
+    haveNonZero = base64Append(sb, (high >> 22) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (high >> 16) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (high >> 10) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (high >> 4) & 0x3f, haveNonZero);
+    int v = ((high & 0xf) << 2) | ((low >> 30) & 0x3);
+    haveNonZero = base64Append(sb, v, haveNonZero);
+    haveNonZero = base64Append(sb, (low >> 24) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (low >> 18) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (low >> 12) & 0x3f, haveNonZero);
+    base64Append(sb, (low >> 6) & 0x3f, haveNonZero);
+    base64Append(sb, low & 0x3f, true);
+
+    return sb.toString();
+  }
+
   public static double toDouble(LongEmul a) {
     if (LongLib.eq(a, Const.MIN_VALUE)) {
       return -9223372036854775808.0;
@@ -502,7 +489,8 @@
     return create(getL(a) ^ getL(b), getM(a) ^ getM(b), getH(a) ^ getH(b));
   }
 
-  private static void base64Append(StringBuilder sb, int digit) {
+  private static boolean base64Append(StringBuilder sb, int digit,
+      boolean haveNonZero) {
     if (digit > 0) {
       haveNonZero = true;
     }
@@ -521,6 +509,7 @@
       }
       sb.append((char) c);
     }
+    return haveNonZero;
   }
 
   // Assume digit is one of [A-Za-z0-9$_]
diff --git a/dev/core/test/com/google/gwt/lang/LongLibTest.java b/dev/core/test/com/google/gwt/lang/LongLibTest.java
index 7591238..b04ab5a 100644
--- a/dev/core/test/com/google/gwt/lang/LongLibTest.java
+++ b/dev/core/test/com/google/gwt/lang/LongLibTest.java
@@ -407,20 +407,20 @@
   }
 
   public static void testBase64() {
-    assertEquals("A", LongLib.base64Emit(0x0L, false));
-    assertEquals(0x0L, LongLib.base64Parse("A"));
+    assertEquals("A", LongLib.toBase64(0x0L));
+    assertEquals(0x0L, LongLib.longFromBase64("A"));
 
-    assertEquals("'B'", LongLib.base64Emit(0x1L, true));
-    assertEquals(0x1L, LongLib.base64Parse("B"));
+    assertEquals("B", LongLib.toBase64(0x1L));
+    assertEquals(0x1L, LongLib.longFromBase64("B"));
 
-    assertEquals("'BA'", LongLib.base64Emit(0x40L, true));
-    assertEquals(0x40L, LongLib.base64Parse("BA"));
+    assertEquals("BA", LongLib.toBase64(0x40L));
+    assertEquals(0x40L, LongLib.longFromBase64("BA"));
 
-    assertEquals("'P_________A'", LongLib.base64Emit(-0x40L, true));
-    assertEquals(-0x40L, LongLib.base64Parse("P_________A"));
+    assertEquals("P_________A", LongLib.toBase64(-0x40L));
+    assertEquals(-0x40L, LongLib.longFromBase64("P_________A"));
 
-    assertEquals("'P__________'", LongLib.base64Emit(-1L, true));
-    assertEquals(-1L, LongLib.base64Parse("P__________"));
+    assertEquals("P__________", LongLib.toBase64(-1L));
+    assertEquals(-1L, LongLib.longFromBase64("P__________"));
 
     // Use all types of base 64 chars
     long value = 0L;
@@ -437,8 +437,8 @@
     value |= 63L;       // '_'
 
     String s = "Pjs$aJSZ05_";
-    assertEquals(s, LongLib.base64Emit(value, false));
-    assertEquals(value, LongLib.base64Parse(s));
+    assertEquals(s, LongLib.toBase64(value));
+    assertEquals(value, LongLib.longFromBase64(s));
   }
 
   public static void testCompare() {
diff --git a/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java b/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java
index 2085fbc..b2028a7 100644
--- a/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java
@@ -103,7 +103,7 @@
       extractData(array, value);
       toReturn = array;
 
-    } else if (value instanceof Enum) {
+    } else if (value instanceof Enum<?>) {
       EnumValueCommand e = new EnumValueCommand();
       e.setValue((Enum<?>) value);
       toReturn = e;
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
index c77e46b..db74b38 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.user.client.rpc.impl;
 
-import com.google.gwt.lang.LongLib;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 
@@ -101,17 +100,7 @@
     append(String.valueOf(fieldValue));
   }
   
-  public void writeLong(long value) {
-    if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) {
-      // Write longs as a pair of doubles for backwards compatibility
-      double[] parts = getAsDoubleArray(value);
-      assert parts != null && parts.length == 2;
-      writeDouble(parts[0]);
-      writeDouble(parts[1]);
-    } else {
-      append(LongLib.base64Emit(value, true));
-    }
-  }
+  public abstract void writeLong(long value);
 
   public void writeObject(Object instance) throws SerializationException {
     if (instance == null) {
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
index eeb5f4c..83f2e61 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
@@ -88,7 +88,7 @@
   @UnsafeNativeLong
   public native long readLong() /*-{
     var s = this.@com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader::results[--this.@com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader::index];
-    return @com.google.gwt.lang.LongLib::base64Parse(Ljava/lang/String;)(s);
+    return @com.google.gwt.lang.LongLib::longFromBase64(Ljava/lang/String;)(s);
   }-*/;
 
   public native short readShort() /*-{
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
index 19ac418..571bdad 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.client.rpc.impl;
 
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.lang.LongLib;
 import com.google.gwt.user.client.rpc.SerializationException;
 
 import java.util.List;
@@ -147,6 +148,11 @@
     return buffer.toString();
   }
 
+  @Override
+  public void writeLong(long value) {
+    append(LongLib.toBase64(value));
+  }
+
   /**
    * Appends a token to the end of the buffer.
    */
diff --git a/user/src/com/google/gwt/user/server/Base64Utils.java b/user/src/com/google/gwt/user/server/Base64Utils.java
index 1909fef..c5db3f8 100644
--- a/user/src/com/google/gwt/user/server/Base64Utils.java
+++ b/user/src/com/google/gwt/user/server/Base64Utils.java
@@ -16,26 +16,28 @@
 package com.google.gwt.user.server;
 
 /**
- * A utility to decode and encode byte arrays as Strings, using only "safe" characters.
+ * A utility to decode and encode byte arrays as Strings, using only "safe"
+ * characters.
  */
 public class Base64Utils {
 
   /**
-   * An array mapping size but values to the characters that will be used to represent them.
-   * Note that this is not identical to the set of characters used by MIME-Base64.
+   * An array mapping size but values to the characters that will be used to
+   * represent them. Note that this is not identical to the set of characters
+   * used by MIME-Base64.
    */
   private static final char[] base64Chars = new char[] {
-    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
-    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
-    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
-    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '$', '_'
-  };
+      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+      'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
+      'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+      'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+      '4', '5', '6', '7', '8', '9', '$', '_'};
 
   /**
-   * An array mapping legal base 64 characters [a-zA-Z0-9$_] to their associated 6-bit values.
-   * The source indices will be given by 7-bit ASCII characters, thus the array size needs to
-   * be 128 (actually 123 would suffice for the given set of characters in use).
+   * An array mapping legal base 64 characters [a-zA-Z0-9$_] to their associated
+   * 6-bit values. The source indices will be given by 7-bit ASCII characters,
+   * thus the array size needs to be 128 (actually 123 would suffice for the
+   * given set of characters in use).
    */
   private static final byte[] base64Values = new byte[128];
 
@@ -60,17 +62,17 @@
     if (data == null) {
       return null;
     }
-    
+
     int len = data.length();
     assert (len % 4) == 0;
-    
+
     if (len == 0) {
       return new byte[0];
     }
-    
+
     char[] chars = new char[len];
     data.getChars(0, len, chars, 0);
-    
+
     int olen = 3 * (len / 4);
     if (chars[len - 2] == '=') {
       --olen;
@@ -78,9 +80,9 @@
     if (chars[len - 1] == '=') {
       --olen;
     }
-    
+
     byte[] bytes = new byte[olen];
-    
+
     int iidx = 0;
     int oidx = 0;
     while (iidx < len) {
@@ -89,28 +91,42 @@
       int c2 = base64Values[chars[iidx++] & 0xff];
       int c3 = base64Values[chars[iidx++] & 0xff];
       int c24 = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;
-      
+
       bytes[oidx++] = (byte) (c24 >> 16);
       if (oidx == olen) {
         break;
       }
-      bytes[oidx++] = (byte) (c24 >>  8);
+      bytes[oidx++] = (byte) (c24 >> 8);
       if (oidx == olen) {
         break;
       }
-      bytes[oidx++] = (byte)  c24;
+      bytes[oidx++] = (byte) c24;
     }
-    
+
     return bytes;
   }
 
   /**
+   * Decode a base64 string into a long value.
+   */
+  public static long longFromBase64(String value) {
+    int pos = 0;
+    long longVal = base64Values[value.charAt(pos++)];
+    int len = value.length();
+    while (pos < len) {
+      longVal <<= 6;
+      longVal |= base64Values[value.charAt(pos++)];
+    }
+    return longVal;
+  }
+
+  /**
    * Converts a byte array into a base 64 encoded string. Null is encoded as
    * null, and an empty array is encoded as an empty string. Otherwise, the byte
    * data is read 3 bytes at a time, with bytes off the end of the array padded
    * with zeros. Each 24-bit chunk is encoded as 4 characters from the sequence
-   * [A-Za-z0-9$_]. If one of the size-bit source positions consists entirely of
-   * padding zeros, an '=' character is used instead.
+   * [A-Za-z0-9$_]. If one of the source positions consists entirely of padding
+   * zeros, an '=' character is used instead.
    * 
    * @param data a byte array, which may be null or empty
    * @return a String
@@ -124,7 +140,7 @@
     if (len == 0) {
       return "";
     }
-    
+
     int olen = 4 * ((len + 2) / 3);
     char[] chars = new char[olen];
 
@@ -152,4 +168,41 @@
 
     return new String(chars);
   }
+
+  /**
+   * Return a string containing a base-64 encoded version of the given long
+   * value.  Leading groups of all zero bits are omitted.
+   */
+  public static String toBase64(long value) {
+    // Convert to ints early to avoid need for long ops
+    int low = (int) (value & 0xffffffff);
+    int high = (int) (value >> 32);
+
+    StringBuilder sb = new StringBuilder();
+    boolean haveNonZero = base64Append(sb, (high >> 28) & 0xf, false);
+    haveNonZero = base64Append(sb, (high >> 22) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (high >> 16) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (high >> 10) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (high >> 4) & 0x3f, haveNonZero);
+    int v = ((high & 0xf) << 2) | ((low >> 30) & 0x3);
+    haveNonZero = base64Append(sb, v, haveNonZero);
+    haveNonZero = base64Append(sb, (low >> 24) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (low >> 18) & 0x3f, haveNonZero);
+    haveNonZero = base64Append(sb, (low >> 12) & 0x3f, haveNonZero);
+    base64Append(sb, (low >> 6) & 0x3f, haveNonZero);
+    base64Append(sb, low & 0x3f, true);
+
+    return sb.toString();
+  }
+
+  private static boolean base64Append(StringBuilder sb, int digit,
+      boolean haveNonZero) {
+    if (digit > 0) {
+      haveNonZero = true;
+    }
+    if (haveNonZero) {
+      sb.append(base64Chars[digit]);
+    }
+    return haveNonZero;
+  }
 }
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 0be3812..4b642e1 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.user.server.rpc.impl;
 
-import com.google.gwt.lang.LongLib;
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader;
@@ -498,7 +497,7 @@
     if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) {
       return (long) readDouble() + (long) readDouble();
     } else {
-      return LongLib.base64Parse(extract());
+      return Base64Utils.longFromBase64(extract());
     }
   }
 
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
index 9710012..b129c9f 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -555,6 +555,23 @@
 
     return stream.toString();
   }
+  
+  @Override
+  public void writeLong(long value) {
+    if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) {
+      // Write longs as a pair of doubles for backwards compatibility
+      double[] parts = getAsDoubleArray(value);
+      assert parts != null && parts.length == 2;
+      writeDouble(parts[0]);
+      writeDouble(parts[1]);
+    } else {
+      StringBuilder sb = new StringBuilder();
+      sb.append('\'');
+      sb.append(Base64Utils.toBase64(value));
+      sb.append('\'');
+      append(sb.toString());
+    }
+  }
 
   @Override
   protected void append(String token) {