Make java.util.Collections.Empty{List,Map,Set} work with GWT RPC.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7606 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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
new file mode 100644
index 0000000..56a77d3
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/Collections.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010 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.user.client.rpc.core.java.util;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Dummy class for nesting the custom serializer.
+ */
+public final class Collections {
+
+  /**
+   * Custom field serializer for {@link java.util.Collections$EmptyList}.
+   */
+  public static final class EmptyList_CustomFieldSerializer {
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void deserialize(SerializationStreamReader streamReader,
+        List instance) throws SerializationException {
+      // Handled in instantiate.
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static List instantiate(SerializationStreamReader streamReader)
+        throws SerializationException {
+      return java.util.Collections.emptyList();
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void serialize(SerializationStreamWriter streamWriter,
+        List instance) throws SerializationException {
+      // Nothing to serialize -- instantiate always returns the same thing
+    }
+  }
+
+  /**
+   * Custom field serializer for {@link java.util.Collections$EmptyMap}.
+   */
+  public static final class EmptyMap_CustomFieldSerializer {
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void deserialize(SerializationStreamReader streamReader,
+        Map instance) throws SerializationException {
+      // Handled in instantiate.
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static Map instantiate(SerializationStreamReader streamReader)
+        throws SerializationException {
+      return java.util.Collections.emptyMap();
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void serialize(SerializationStreamWriter streamWriter,
+        Map instance) throws SerializationException {
+      // Nothing to serialize -- instantiate always returns the same thing
+    }
+  }
+
+  /**
+   * Custom field serializer for {@link java.util.Collections$EmptySet}.
+   */
+  public static final class EmptySet_CustomFieldSerializer {
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void deserialize(SerializationStreamReader streamReader,
+        Set instance) throws SerializationException {
+      // Handled in instantiate.
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static Set instantiate(SerializationStreamReader streamReader)
+        throws SerializationException {
+      return java.util.Collections.emptySet();
+    }
+
+    @SuppressWarnings({"unused", "unchecked"})
+    public static void serialize(SerializationStreamWriter streamWriter,
+        Set instance) throws SerializationException {
+      // Nothing to serialize -- instantiate always returns the same thing
+    }
+  }
+}
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 3df9d3a..36c1031 100644
--- a/user/super/com/google/gwt/emul/java/util/Collections.java
+++ b/user/super/com/google/gwt/emul/java/util/Collections.java
@@ -15,12 +15,101 @@
  */
 package java.util;
 
+import java.io.Serializable;
+
 /**
  * Utility methods that operate on collections. <a
  * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/Collections.html">[Sun
  * docs]</a>
  */
 public class Collections {
+  
+  private static final class EmptyList extends AbstractList implements
+      RandomAccess, Serializable {
+    @Override
+    public boolean contains(Object object) {
+      return false;
+    }
+
+    @Override
+    public Object get(int location) {
+      throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+  }
+  
+  private static final class EmptySet extends AbstractSet implements
+      Serializable {
+    @Override
+    public boolean contains(Object object) {
+      return false;
+    }
+
+    @Override
+    public Iterator iterator() {
+      return new Iterator() {
+        public boolean hasNext() {
+          return false;
+        }
+
+        public Object next() {
+          throw new NoSuchElementException();
+        }
+
+        public void remove() {
+          throw new UnsupportedOperationException();
+        }
+      };
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+}
+
+  private static final class EmptyMap extends AbstractMap implements
+      Serializable {
+    @Override
+    public boolean containsKey(Object key) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+      return false;
+    }
+
+    @Override
+    public Set entrySet() {
+      return EMPTY_SET;
+    }
+
+    @Override
+    public Object get(Object key) {
+      return null;
+    }
+
+    @Override
+    public Set keySet() {
+      return EMPTY_SET;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public Collection values() {
+      return EMPTY_LIST;
+    }
+  }
+
   /*
    * TODO: make the unmodifiable collections serializable.
    */
@@ -510,13 +599,13 @@
   }
 
   @SuppressWarnings("unchecked")
-  public static final List EMPTY_LIST = unmodifiableList(new ArrayList());
+  public static final List EMPTY_LIST = new EmptyList();
 
   @SuppressWarnings("unchecked")
-  public static final Map EMPTY_MAP = unmodifiableMap(new HashMap());
+  public static final Map EMPTY_MAP = new EmptyMap();
 
   @SuppressWarnings("unchecked")
-  public static final Set EMPTY_SET = unmodifiableSet(new HashSet());
+  public static final Set EMPTY_SET = new EmptySet();
 
   private static Comparator<Comparable<Object>> reverseComparator = new Comparator<Comparable<Object>>() {
     public int compare(Comparable<Object> o1, Comparable<Object> o2) {
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 fae04be..2b32fbd 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArrayList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArraysAsList;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmpty;
 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;
@@ -34,6 +35,8 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -191,6 +194,57 @@
     });
   }
 
+  public void testEmptyList() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    delayTestFinishForRpc();
+    service.echo(TestSetFactory.createEmptyList(),
+        new AsyncCallback<List<MarkerTypeEmpty>>() {
+          public void onFailure(Throwable caught) {
+            TestSetValidator.rethrowException(caught);
+          }
+
+          public void onSuccess(List<MarkerTypeEmpty> result) {
+            assertNotNull(result);
+            assertTrue(TestSetValidator.isValid(result));
+            finishTest();
+          }
+        });
+  }
+
+  public void testEmptyMap() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    delayTestFinishForRpc();
+    service.echo(TestSetFactory.createEmptyMap(),
+        new AsyncCallback<Map<MarkerTypeEmpty, MarkerTypeEmpty>>() {
+          public void onFailure(Throwable caught) {
+            TestSetValidator.rethrowException(caught);
+          }
+
+          public void onSuccess(Map<MarkerTypeEmpty, MarkerTypeEmpty> result) {
+            assertNotNull(result);
+            assertTrue(TestSetValidator.isValid(result));
+            finishTest();
+          }
+        });
+  }
+
+  public void testEmptySet() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    delayTestFinishForRpc();
+    service.echo(TestSetFactory.createEmptySet(),
+        new AsyncCallback<Set<MarkerTypeEmpty>>() {
+          public void onFailure(Throwable caught) {
+            TestSetValidator.rethrowException(caught);
+          }
+
+          public void onSuccess(Set<MarkerTypeEmpty> result) {
+            assertNotNull(result);
+            assertTrue(TestSetValidator.isValid(result));
+            finishTest();
+          }
+        });
+  }
+
   public void testEnumArray() {
     CollectionsTestServiceAsync service = getServiceAsync();
     final Enum<?>[] expected = TestSetFactory.createEnumArray();
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 671a8ac..4902177 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArrayList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArraysAsList;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmpty;
 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;
@@ -34,6 +35,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -58,6 +61,18 @@
   ArrayList<MarkerTypeArrayList> echo(ArrayList<MarkerTypeArrayList> value)
       throws CollectionsTestServiceException;
 
+  // For Collections.emptyList()
+  List<MarkerTypeEmpty> echo(List<MarkerTypeEmpty> value)
+  throws CollectionsTestServiceException;
+
+  // For Collections.emptyMap()
+  Map<MarkerTypeEmpty, MarkerTypeEmpty> echo(Map<MarkerTypeEmpty,
+      MarkerTypeEmpty> value) throws CollectionsTestServiceException;
+
+  // For Collections.emptySet()
+  Set<MarkerTypeEmpty> echo(Set<MarkerTypeEmpty> value)
+  throws CollectionsTestServiceException;
+
   boolean[] echo(boolean[] value) throws CollectionsTestServiceException;
 
   Boolean[] echo(Boolean[] 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 ae55e5c..24221f3 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArrayList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArraysAsList;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmpty;
 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;
@@ -34,6 +35,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -45,6 +48,18 @@
   void echo(ArrayList<MarkerTypeArrayList> value,
       AsyncCallback<ArrayList<MarkerTypeArrayList>> callback);
 
+  // For Collections.emptyList()
+  void echo(List<MarkerTypeEmpty> value,
+      AsyncCallback<List<MarkerTypeEmpty>> callback);
+
+  // For Collections.emptyMap()
+  void echo(Map<MarkerTypeEmpty, MarkerTypeEmpty> value,
+      AsyncCallback<Map<MarkerTypeEmpty, MarkerTypeEmpty>> callback);
+
+  // For Collections.emptySet()
+  void echo(Set<MarkerTypeEmpty> value,
+      AsyncCallback<Set<MarkerTypeEmpty>> callback);
+
   void echo(boolean[] value, AsyncCallback<boolean[]> callback);
 
   void echo(Boolean[] value, AsyncCallback<Boolean[]> 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 069a7fb..5b75365 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
@@ -27,6 +27,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -103,6 +105,16 @@
 
   /**
    * A single-use marker type to independently check type parameter exposure in
+   * various empty collections.
+   */
+  public static final class MarkerTypeEmpty extends MarkerBase {
+    MarkerTypeEmpty() {
+      super(null);
+    }
+  }
+
+  /**
+   * A single-use marker type to independently check type parameter exposure in
    * various collections.
    */
   public static enum MarkerTypeEnum {
@@ -394,6 +406,18 @@
         new Double(Double.MAX_VALUE), new Double(Double.MIN_VALUE)};
   }
 
+  public static List<MarkerTypeEmpty> createEmptyList() {
+    return java.util.Collections.emptyList();
+  }
+
+  public static Map<MarkerTypeEmpty, MarkerTypeEmpty> createEmptyMap() {
+    return java.util.Collections.emptyMap();
+  }
+
+  public static Set<MarkerTypeEmpty> createEmptySet() {
+    return java.util.Collections.emptySet();
+  }
+
   public static Enum<?>[] createEnumArray() {
     return new Enum<?>[] {
         MarkerTypeEnum.A, MarkerTypeEnum.B, MarkerTypeEnum.C, MarkerTypeEnum.A,};
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 9093b6f7..91e1743 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -15,6 +15,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.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
@@ -248,6 +249,18 @@
     return reference.equals(list);
   }
 
+  public static boolean isValid(List<MarkerTypeEmpty> list) {
+    return list != null && list.size() == 0;
+  }
+
+  public static boolean isValid(Map<MarkerTypeEmpty, MarkerTypeEmpty> map) {
+    return map != null && map.size() == 0;
+  }
+
+  public static boolean isValid(Set<MarkerTypeEmpty> set) {
+    return set != null && set.size() == 0;
+  }
+
   public static boolean isValid(HashMap<?, ?> expected, HashMap<?, ?> map) {
     if (map == null) {
       return false;
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 dd157c5..2c15ab7 100644
--- a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
@@ -20,6 +20,7 @@
 import com.google.gwt.user.client.rpc.TestSetValidator;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArrayList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArraysAsList;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmpty;
 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;
@@ -38,6 +39,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -267,6 +270,15 @@
     return actual;
   }
 
+  public List<MarkerTypeEmpty> echo(List<MarkerTypeEmpty> list)
+      throws CollectionsTestServiceException {
+    if (!TestSetValidator.isValid(list)) {
+      throw new CollectionsTestServiceException();
+    }
+
+    return list;
+  }
+
   public long[] echo(long[] actual) throws CollectionsTestServiceException {
     long[] expected = TestSetFactory.createPrimitiveLongArray();
     if (!TestSetValidator.equals(expected, actual)) {
@@ -287,6 +299,25 @@
     return actual;
   }
 
+  public Map<MarkerTypeEmpty, MarkerTypeEmpty> echo(
+      Map<MarkerTypeEmpty, MarkerTypeEmpty> map)
+      throws CollectionsTestServiceException {
+    if (!TestSetValidator.isValid(map)) {
+      throw new CollectionsTestServiceException();
+    }
+
+    return map;
+  }
+
+  public Set<MarkerTypeEmpty> echo(Set<MarkerTypeEmpty> set)
+      throws CollectionsTestServiceException {
+    if (!TestSetValidator.isValid(set)) {
+      throw new CollectionsTestServiceException();
+    }
+
+    return set;
+  }
+
   public short[] echo(short[] actual) throws CollectionsTestServiceException {
     short[] expected = TestSetFactory.createPrimitiveShortArray();
     if (!TestSetValidator.equals(expected, actual)) {