Implement RPC custom serialization for Collections.singletonList()

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

Review by: jat@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7946 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/lang/Long_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/java/lang/Long_CustomFieldSerializer.java
index c8075cf..4b416b5 100644
--- a/user/src/com/google/gwt/user/client/rpc/core/java/lang/Long_CustomFieldSerializer.java
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/lang/Long_CustomFieldSerializer.java
@@ -32,7 +32,7 @@
 
   public static Long instantiate(SerializationStreamReader streamReader)
       throws SerializationException {
-    return new Long(streamReader.readLong());
+    return Long.valueOf(streamReader.readLong());
   }
 
   public static void serialize(SerializationStreamWriter streamWriter,
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/lang/Void_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/java/lang/Void_CustomFieldSerializer.java
index 240edf8..ad1cf52 100644
--- a/user/src/com/google/gwt/user/client/rpc/core/java/lang/Void_CustomFieldSerializer.java
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/lang/Void_CustomFieldSerializer.java
@@ -24,15 +24,18 @@
  */
 public final class Void_CustomFieldSerializer {
 
+  @SuppressWarnings("unused")
   public static void deserialize(SerializationStreamReader streamReader,
       Void instance) {
   }
 
+  @SuppressWarnings("unused")
   public static Void instantiate(SerializationStreamReader streamReader)
       throws SerializationException {
     return null;
   }
 
+  @SuppressWarnings("unused")
   public static void serialize(SerializationStreamWriter streamWriter,
       Void instance) throws SerializationException {
   }
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/util/Collections.java b/user/src/com/google/gwt/user/client/rpc/core/java/util/Collections.java
index 56a77d3..4c24a79 100644
--- a/user/src/com/google/gwt/user/client/rpc/core/java/util/Collections.java
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/Collections.java
@@ -99,4 +99,27 @@
       // Nothing to serialize -- instantiate always returns the same thing
     }
   }
+
+  /**
+   * Custom field serializer for {@link java.util.Collections$SingletonList}.
+   */
+  public static final class SingletonList_CustomFieldSerializer {
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void deserialize(SerializationStreamReader streamReader,
+        List instance) throws SerializationException {
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static List instantiate(SerializationStreamReader streamReader)
+        throws SerializationException {
+      return java.util.Collections.singletonList(streamReader.readObject());
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void serialize(SerializationStreamWriter streamWriter,
+        List instance) throws SerializationException {
+      streamWriter.writeObject(instance.get(0));
+    }
+  }
 }
diff --git a/user/super/com/google/gwt/emul/java/util/Collections.java b/user/super/com/google/gwt/emul/java/util/Collections.java
index 36c1031..bd2451a 100644
--- a/user/super/com/google/gwt/emul/java/util/Collections.java
+++ b/user/super/com/google/gwt/emul/java/util/Collections.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,7 +23,7 @@
  * docs]</a>
  */
 public class Collections {
-  
+
   private static final class EmptyList extends AbstractList implements
       RandomAccess, Serializable {
     @Override
@@ -41,7 +41,7 @@
       return 0;
     }
   }
-  
+
   private static final class EmptySet extends AbstractSet implements
       Serializable {
     @Override
@@ -110,6 +110,30 @@
     }
   }
 
+  private static final class SingletonList<E> extends AbstractList<E> implements Serializable {
+    private E element;
+
+    public SingletonList(E element) {
+      this.element = element;
+    }
+
+    public boolean contains(Object item) {
+      return Utility.equalsWithNullCheck(element, item);
+    }
+
+    public E get(int index) {
+      if (index == 0) {
+        return element;
+      } else {
+        throw new IndexOutOfBoundsException();
+      }
+    }
+
+    public int size() {
+      return 1;
+    }
+  }
+
   /*
    * TODO: make the unmodifiable collections serializable.
    */
@@ -172,7 +196,7 @@
     public <E> E[] toArray(E[] a) {
       return coll.toArray(a);
     }
-    
+
     public String toString() {
       return coll.toString();
     }
@@ -335,7 +359,7 @@
 
       /**
        * Wrap an array of Map.Entries as UnmodifiableEntries.
-       * 
+       *
        * @param array array to wrap
        * @param size number of entries to wrap
        */
@@ -488,11 +512,11 @@
     public K lastKey() {
       return sortedMap.lastKey();
     }
-    
+
     public SortedMap<K, V> subMap(K fromKey, K toKey) {
       return new UnmodifiableSortedMap<K, V>(sortedMap.subMap(fromKey, toKey));
     }
-    
+
     public SortedMap<K, V> tailMap(K fromKey) {
       return new UnmodifiableSortedMap<K, V>(sortedMap.tailMap(fromKey));
     }
@@ -511,7 +535,7 @@
     public Comparator<? super E> comparator() {
       return sortedSet.comparator();
     }
-    
+
     @Override
     public boolean equals(Object o) {
       return sortedSet.equals(o);
@@ -520,7 +544,7 @@
     public E first() {
       return sortedSet.first();
     }
-    
+
     @Override
     public int hashCode() {
       return sortedSet.hashCode();
@@ -623,13 +647,13 @@
 
   /**
    * Perform a binary search on a sorted List, using natural ordering.
-   * 
+   *
    * <p>
    * Note: The GWT implementation differs from the JDK implementation in that it
    * does not do an iterator-based binary search for Lists that do not implement
    * RandomAccess.
    * </p>
-   * 
+   *
    * @param sortedList object array to search
    * @param key value to search for
    * @return the index of an element with a matching value, or a negative number
@@ -653,7 +677,7 @@
   // // FUTURE: implement
   // return null;
   // }
-  //  
+  //
   // static <E> List<E> checkedList(List<E> list, Class<E> type) {
   // // FUTURE: implement
   // return null;
@@ -684,13 +708,13 @@
   /**
    * Perform a binary search on a sorted List, using a user-specified comparison
    * function.
-   * 
+   *
    * <p>
    * Note: The GWT implementation differs from the JDK implementation in that it
    * does not do an iterator-based binary search for Lists that do not implement
    * RandomAccess.
    * </p>
-   * 
+   *
    * @param sortedList List to search
    * @param key value to search for
    * @param comparator comparision function, <code>null</code> indicates
@@ -911,9 +935,7 @@
   // More efficient at runtime, but more code bloat to download
 
   public static <T> List<T> singletonList(T o) {
-    List<T> list = new ArrayList<T>(1);
-    list.add(o);
-    return unmodifiableList(list);
+    return new SingletonList<T>(o);
   }
 
   public static <K, V> Map<K, V> singletonMap(K key, V value) {
@@ -971,7 +993,7 @@
 
   /**
    * Replace contents of a list from an array.
-   * 
+   *
    * @param <T> element type
    * @param target list to replace contents from an array
    * @param x an Object array which can contain only T instances
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
index eb718d3..da06099 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -22,6 +22,7 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeSingleton;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
@@ -564,6 +565,23 @@
     });
   }
 
+  public void testSingletonList() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    delayTestFinishForRpc();
+    service.echoSingletonList(TestSetFactory.createSingletonList(),
+        new AsyncCallback<List<MarkerTypeSingleton>>() {
+          public void onFailure(Throwable caught) {
+            TestSetValidator.rethrowException(caught);
+          }
+
+          public void onSuccess(List<MarkerTypeSingleton> result) {
+            assertNotNull(result);
+            assertTrue(TestSetValidator.isValidSingletonList(result));
+            finishTest();
+          }
+        });
+  }
+
   public void testSqlDateArray() {
     CollectionsTestServiceAsync service = getServiceAsync();
     final java.sql.Date[] expected = TestSetFactory.createSqlDateArray();
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
index 9c0b520..8ae7c93 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
@@ -22,6 +22,7 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeSingleton;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
@@ -151,4 +152,8 @@
   List<MarkerTypeArraysAsList> echoArraysAsList(
       List<MarkerTypeArraysAsList> value)
       throws CollectionsTestServiceException;
+
+  // For Collections.singletonList()
+  List<MarkerTypeSingleton> echoSingletonList(List<MarkerTypeSingleton> value)
+      throws CollectionsTestServiceException;
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
index e6c5e38..10732bd 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
@@ -22,6 +22,7 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeSingleton;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
@@ -132,4 +133,8 @@
 
   void echoArraysAsList(List<MarkerTypeArraysAsList> value,
       AsyncCallback<List<MarkerTypeArraysAsList>> callback);
+
+  // For Collections.singletonList()
+  void echoSingletonList(List<MarkerTypeSingleton> value,
+      AsyncCallback<List<MarkerTypeSingleton>> callback);
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
index 7f749d9..9ae14ed 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
@@ -183,6 +183,16 @@
 
   /**
    * A single-use marker type to independently check type parameter exposure in
+   * singleton collections.
+   */
+  public static final class MarkerTypeSingleton extends MarkerBase {
+    MarkerTypeSingleton() {
+      super("singleton");
+    }
+  }
+
+  /**
+   * A single-use marker type to independently check type parameter exposure in
    * various collections.
    */
   public static final class MarkerTypeTreeMap extends MarkerBase {
@@ -550,6 +560,10 @@
         new Short(Short.MAX_VALUE), new Short(Short.MIN_VALUE)};
   }
 
+  public static List<MarkerTypeSingleton> createSingletonList() {
+    return java.util.Collections.singletonList(new MarkerTypeSingleton());
+  }
+
   public static java.sql.Date[] createSqlDateArray() {
     return new java.sql.Date[] {
         new java.sql.Date(500L), new java.sql.Date(500000000L)};
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
index 8bb3bd6..89f5c76 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,6 +16,7 @@
 package com.google.gwt.user.client.rpc;
 
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmpty;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeSingleton;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
@@ -534,6 +535,22 @@
     return true;
   }
 
+  public static boolean isValidSingletonList(List<MarkerTypeSingleton> list) {
+    if (list == null || list.size() != 1) {
+      return false;
+    }
+    Object value = list.get(0);
+    // Perform instanceof check in case RPC did the wrong thing
+    if (!(value instanceof MarkerTypeSingleton)) {
+      return false;
+    }
+    MarkerTypeSingleton singleton = (MarkerTypeSingleton) value;
+    if (!"singleton".equals(singleton.getValue())) {
+      return false;
+    }
+    return true;
+  }
+
   public static boolean isValidTrivialCyclicGraph(
       SerializableDoublyLinkedNode actual) {
     if (actual == null) {
@@ -564,7 +581,7 @@
   /**
    * Wrap an exception in RuntimeException if necessary so it doesn't have to be
    * listed in throws clauses.
-   * 
+   *
    * @param caught exception to wrap
    */
   public static void rethrowException(Throwable caught) {
diff --git a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
index 0b5ca20..f0ce23a 100644
--- a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
@@ -25,6 +25,7 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeSingleton;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
@@ -429,4 +430,12 @@
     return value;
   }
 
+  public List<MarkerTypeSingleton> echoSingletonList(
+      List<MarkerTypeSingleton> value) throws CollectionsTestServiceException {
+    if (!TestSetValidator.isValidSingletonList(value)) {
+      throw new CollectionsTestServiceException();
+    }
+
+    return value;
+  }
 }