Implementing RPC for java.util.Arrays.ArrayList.

Getting this to work required changing how custom field serializers are handled.
- CustomFieldSerializerValidator would only verify assignability rather than exact type match on the second argument of a custom field serializer's serialize and deserialize methods.
- However, both the generated TypeSerializer and the server implementations would die horrible deaths if the types did not match exactly.
- Since I needed the looser behavior to implement my custom field serializer anyway, I went ahead and changed TypeSerializerCreator and the ServerSerializationStreams to work with looser declared types.

Review by: bobv (desk)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2472 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/util/Arrays.java b/user/src/com/google/gwt/user/client/rpc/core/java/util/Arrays.java
new file mode 100644
index 0000000..eb4e4b8
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/Arrays.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 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.core.client.GWT;
+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;
+
+/**
+ * Dummy class for nesting the custom serializer.
+ */
+public final class Arrays {
+
+  /**
+   * Custom field serializer for {@link java.util.Arrays$ArrayList}.
+   */
+  public static final class ArrayList_CustomFieldSerializer {
+    /*
+     * Note: the reason this implementation differs from that of a standard List
+     * (which serializes a number and then each element) is the requirement that
+     * the underlying array retain its correct type across the wire. This gives
+     * toArray() results the correct type, and can generate internal
+     * ArrayStoreExceptions.
+     */
+
+    public static void deserialize(SerializationStreamReader streamReader,
+        List instance) throws SerializationException {
+      // Handled in instantiate.
+    }
+
+    public static List instantiate(SerializationStreamReader streamReader)
+        throws SerializationException {
+      Object[] array = (Object[]) streamReader.readObject();
+      return java.util.Arrays.asList(array);
+    }
+
+    public static void serialize(SerializationStreamWriter streamWriter,
+        List instance) throws SerializationException {
+      Object[] array;
+      if (GWT.isScript()) {
+        // Violator pattern.
+        array = getArray0(instance);
+      } else {
+        // Clone the underlying array.
+        array = instance.toArray();
+      }
+      streamWriter.writeObject(array);
+    }
+
+    private static native Object[] getArray0(List instance) /*-{
+      return instance.@java.util.Arrays$ArrayList::array;
+    }-*/;
+  }
+}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java b/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
index e01c7ed..6158b31 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
@@ -20,6 +20,8 @@
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -32,6 +34,7 @@
   private static final String NO_DESERIALIZE_METHOD = "Custom Field Serializer ''{0}'' does not define a deserialize method: ''public static void deserialize({1} reader,{2} instance)''";
   private static final String NO_INSTANTIATE_METHOD = "Custom Field Serializer ''{0}'' does not define an instantiate method: ''public static {1} instantiate({2} reader)''; but ''{1}'' is not default instantiable";
   private static final String NO_SERIALIZE_METHOD = "Custom Field Serializer ''{0}'' does not define a serialize method: ''public static void serialize({1} writer,{2} instance)''";
+  private static final String TOO_MANY_METHODS = "Custom Field Serializer ''{0}'' defines too many methods named ''{1}''; please define only one method with that name";
 
   /**
    * Returns a list of error messages associated with the custom field
@@ -46,8 +49,8 @@
    * @return list of error messages, if any, associated with the custom field
    *         serializer
    */
-  public static List<String> validate(JClassType streamReaderClass,
-      JClassType streamWriterClass, JClassType serializer, JClassType serializee) {
+  public static List<String> validate(JClassType serializer,
+      JClassType serializee) {
     List<String> reasons = new ArrayList<String>();
 
     if (serializee.isEnum() != null) {
@@ -60,39 +63,49 @@
       return reasons;
     }
 
-    if (!hasSerializationMethod(streamReaderClass, "deserialize", serializer,
-        serializee)) {
+    if (!hasDeserializationMethod(serializer, serializee)) {
       // No valid deserialize method was found.
       reasons.add(MessageFormat.format(NO_DESERIALIZE_METHOD,
           serializer.getQualifiedSourceName(),
-          streamReaderClass.getQualifiedSourceName(),
+          SerializationStreamReader.class.getName(),
           serializee.getQualifiedSourceName()));
+    } else {
+      checkTooMany("deserialize", serializer, reasons);
     }
 
-    if (!hasSerializationMethod(streamWriterClass, "serialize", serializer,
-        serializee)) {
+    if (!hasSerializationMethod(serializer, serializee)) {
       // No valid serialize method was found.
       reasons.add(MessageFormat.format(NO_SERIALIZE_METHOD,
           serializer.getQualifiedSourceName(),
-          streamWriterClass.getQualifiedSourceName(),
+          SerializationStreamWriter.class.getName(),
           serializee.getQualifiedSourceName()));
+    } else {
+      checkTooMany("serialize", serializer, reasons);
     }
 
-    if (!serializee.isDefaultInstantiable() && !serializee.isAbstract()) {
-      if (!hasInstantiationMethod(streamReaderClass, serializer, serializee)) {
+    if (!hasInstantiationMethod(serializer, serializee)) {
+      if (!serializee.isDefaultInstantiable() && !serializee.isAbstract()) {
         // Not default instantiable and no instantiate method was found.
         reasons.add(MessageFormat.format(NO_INSTANTIATE_METHOD,
             serializer.getQualifiedSourceName(),
             serializee.getQualifiedSourceName(),
-            streamReaderClass.getQualifiedSourceName()));
+            SerializationStreamReader.class.getName()));
       }
+    } else {
+      checkTooMany("instantiate", serializer, reasons);
     }
 
     return reasons;
   }
 
-  private static boolean hasInstantiationMethod(JClassType streamReaderClass,
-      JClassType serializer, JClassType serializee) {
+  static JMethod getDeserializationMethod(JClassType serializer,
+      JClassType serializee) {
+    return getMethod("deserialize", SerializationStreamReader.class.getName(),
+        serializer, serializee);
+  }
+
+  static JMethod getInstantiationMethod(JClassType serializer,
+      JClassType serializee) {
     JMethod[] overloads = serializer.getOverloads("instantiate");
     for (JMethod overload : overloads) {
       JParameter[] parameters = overload.getParameters();
@@ -102,7 +115,8 @@
         continue;
       }
 
-      if (parameters[0].getType() != streamReaderClass) {
+      if (!parameters[0].getType().getQualifiedSourceName().equals(
+          SerializationStreamReader.class.getName())) {
         // First param is not a stream class
         continue;
       }
@@ -120,14 +134,46 @@
       // TODO: if isArray answered yes to isClass this cast would not be
       // necessary
       JClassType clazz = (JClassType) type;
-      return clazz.isAssignableFrom(serializee);
+      if (clazz.isAssignableFrom(serializee)) {
+        return overload;
+      }
     }
 
-    return false;
+    return null;
   }
 
-  private static boolean hasSerializationMethod(JClassType streamClass,
-      String methodName, JClassType serializer, JClassType serializee) {
+  static JMethod getSerializationMethod(JClassType serializer,
+      JClassType serializee) {
+    return getMethod("serialize", SerializationStreamWriter.class.getName(),
+        serializer, serializee);
+  }
+
+  static boolean hasDeserializationMethod(JClassType serializer,
+      JClassType serializee) {
+    return getDeserializationMethod(serializer, serializee) != null;
+  }
+
+  static boolean hasInstantiationMethod(JClassType serializer,
+      JClassType serializee) {
+    return getInstantiationMethod(serializer, serializee) != null;
+  }
+
+  static boolean hasSerializationMethod(JClassType serializer,
+      JClassType serializee) {
+    return getSerializationMethod(serializer, serializee) != null;
+  }
+
+  private static void checkTooMany(String methodName, JClassType serializer,
+      List<String> reasons) {
+    JMethod[] overloads = serializer.getOverloads(methodName);
+    if (overloads.length > 1) {
+      reasons.add(MessageFormat.format(TOO_MANY_METHODS,
+          serializer.getQualifiedSourceName(), methodName));
+    }
+  }
+
+  private static JMethod getMethod(String methodName, String streamClassName,
+      JClassType serializer, JClassType serializee) {
     JMethod[] overloads = serializer.getOverloads(methodName);
     for (JMethod overload : overloads) {
       JParameter[] parameters = overload.getParameters();
@@ -137,7 +183,8 @@
         continue;
       }
 
-      if (parameters[0].getType() != streamClass) {
+      if (!parameters[0].getType().getQualifiedSourceName().equals(
+          streamClassName)) {
         // First param is not a stream class
         continue;
       }
@@ -155,12 +202,12 @@
       if (clazz.isAssignableFrom(serializee)) {
         if (isValidCustomFieldSerializerMethod(overload)
             && overload.getReturnType() == JPrimitiveType.VOID) {
-          return true;
+          return overload;
         }
       }
     }
 
-    return false;
+    return null;
   }
 
   private static boolean isValidCustomFieldSerializerMethod(JMethod method) {
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index 5f25e77..90203ac 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -33,8 +33,6 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.IsSerializable;
-import com.google.gwt.user.client.rpc.SerializationStreamReader;
-import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 
 import java.io.Serializable;
 import java.text.MessageFormat;
@@ -546,16 +544,6 @@
    */
   private final JClassType serializableClass;
 
-  /**
-   * Cache of the {@link JClassType} for {@link SerializationStreamReader}.
-   */
-  private final JClassType streamReaderClass;
-
-  /**
-   * Cache of the {@link JClassType} for {@link SerializationStreamWriter}.
-   */
-  private final JClassType streamWriterClass;
-
   private final TypeOracle typeOracle;
 
   /**
@@ -587,8 +575,6 @@
       isSerializableClass = typeOracle.getType(IsSerializable.class.getName());
       mapClass = typeOracle.getType(Map.class.getName());
       serializableClass = typeOracle.getType(Serializable.class.getName());
-      streamReaderClass = typeOracle.getType(SerializationStreamReader.class.getName());
-      streamWriterClass = typeOracle.getType(SerializationStreamWriter.class.getName());
     } catch (NotFoundException e) {
       rootLogger.log(TreeLogger.ERROR, null, e);
       throw new UnableToCompleteException();
@@ -892,8 +878,7 @@
 
     if (typeInfo.isManuallySerializable()) {
       List<String> failures = CustomFieldSerializerValidator.validate(
-          streamReaderClass, streamWriterClass, typeInfo.getManualSerializer(),
-          type);
+          typeInfo.getManualSerializer(), type);
       if (!failures.isEmpty()) {
         markAsUninstantiableAndLog(logger, isSpeculative, failures, tic);
         return false;
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
index 3a12ab4..7fefb93 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
@@ -273,10 +273,10 @@
         srcWriter.println("[");
         {
           srcWriter.indent();
-
           String serializerName = serializationOracle.getFieldSerializerName(type);
+
+          // First the initialization method
           {
-            // First the initialization method
             srcWriter.print("@");
             if (needsCreateMethod(type)) {
               srcWriter.print(serializationOracle.getTypeSerializerQualifiedName(getServiceInterface()));
@@ -292,22 +292,39 @@
             srcWriter.println(",");
           }
 
-          String jsniSignature = type.getJNISignature();
+          JClassType customSerializer = serializationOracle.hasCustomFieldSerializer(type);
 
+          // Now the deserialization method
           {
-            // Now the deserialization method
+            // Assume param type is the concrete type of the serialized type.
+            JType paramType = type;
+            if (customSerializer != null) {
+              // But a custom serializer may specify a looser type.
+              JMethod deserializationMethod = CustomFieldSerializerValidator.getDeserializationMethod(
+                  customSerializer, (JClassType) type);
+              paramType = deserializationMethod.getParameters()[1].getType();
+            }
             srcWriter.print("@" + serializerName);
             srcWriter.print("::deserialize(L"
                 + SerializationStreamReader.class.getName().replace('.', '/')
-                + ";" + jsniSignature + ")");
+                + ";" + paramType.getJNISignature() + ")");
             srcWriter.println(",");
           }
+
+          // Now the serialization method
           {
-            // Now the serialization method
+            // Assume param type is the concrete type of the serialized type.
+            JType paramType = type;
+            if (customSerializer != null) {
+              // But a custom serializer may specify a looser type.
+              JMethod serializationMethod = CustomFieldSerializerValidator.getSerializationMethod(
+                  customSerializer, (JClassType) type);
+              paramType = serializationMethod.getParameters()[1].getType();
+            }
             srcWriter.print("@" + serializerName);
             srcWriter.print("::serialize(L"
                 + SerializationStreamWriter.class.getName().replace('.', '/')
-                + ";" + jsniSignature + ")");
+                + ";" + paramType.getJNISignature() + ")");
             srcWriter.println();
           }
           srcWriter.outdent();
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 4d568fc..eca479f 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
@@ -16,7 +16,6 @@
 package com.google.gwt.user.server.rpc.impl;
 
 import com.google.gwt.user.client.rpc.SerializationException;
-import com.google.gwt.user.client.rpc.SerializationStreamReader;
 import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader;
 import com.google.gwt.user.server.rpc.RPC;
 import com.google.gwt.user.server.rpc.SerializationPolicy;
@@ -596,9 +595,13 @@
       InvocationTargetException {
     assert (!instanceClass.isArray());
 
-    Method deserialize = customSerializer.getMethod("deserialize",
-        SerializationStreamReader.class, instanceClass);
-    deserialize.invoke(null, this, instance);
+    for (Method method : customSerializer.getMethods()) {
+      if ("deserialize".equals(method.getName())) {
+        method.invoke(null, this, instance);
+        return;
+      }
+    }
+    throw new NoSuchMethodException("deserialize");
   }
 
   private String extract() {
@@ -610,13 +613,12 @@
       IllegalArgumentException, InvocationTargetException,
       NoSuchMethodException {
     if (customSerializer != null) {
-      try {
-        Method instantiate = customSerializer.getMethod("instantiate",
-            SerializationStreamReader.class);
-        return instantiate.invoke(null, this);
-      } catch (NoSuchMethodException e) {
-        // purposely ignored
+      for (Method method : customSerializer.getMethods()) {
+        if ("instantiate".equals(method.getName())) {
+          return method.invoke(null, this);
+        }
       }
+      // Ok to not have one.
     }
 
     if (instanceClass.isArray()) {
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 499ea9b..6946d47 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
@@ -16,7 +16,6 @@
 package com.google.gwt.user.server.rpc.impl;
 
 import com.google.gwt.user.client.rpc.SerializationException;
-import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter;
 import com.google.gwt.user.server.rpc.SerializationPolicy;
 
@@ -660,15 +659,16 @@
   private void serializeWithCustomSerializer(Class<?> customSerializer,
       Object instance, Class<?> instanceClass) throws SerializationException {
 
-    Method serialize;
     try {
       assert (!instanceClass.isArray());
 
-      serialize = customSerializer.getMethod("serialize",
-          SerializationStreamWriter.class, instanceClass);
-
-      serialize.invoke(null, this, instance);
-
+      for (Method method : customSerializer.getMethods()) {
+        if ("serialize".equals(method.getName())) {
+          method.invoke(null, this, instance);
+          return;
+        }
+      }
+      throw new NoSuchMethodException("serialize");
     } catch (SecurityException e) {
       throw new SerializationException(e);
 
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 b151e17..214dea4 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -101,6 +101,25 @@
     });
   }
 
+  public void testArraysAsList() {
+    delayTestFinish(TEST_DELAY);
+
+    CollectionsTestServiceAsync service = getServiceAsync();
+    final List expected = TestSetFactory.createArraysAsList();
+
+    service.echoArraysAsList(expected, new AsyncCallback() {
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      public void onSuccess(Object result) {
+        assertNotNull(result);
+        assertEquals(expected, result);
+        finishTest();
+      }
+    });
+  }
+
   public void testBooleanArray() {
     delayTestFinish(TEST_DELAY);
 
@@ -173,34 +192,6 @@
     });
   }
 
-  /**
-   * This method checks that attempting to return
-   * {@link java.util.Arrays#asList(Object[])} from the server will result in an
-   * InvocationException on the client.
-   */
-  public void testFailureWhenReturningArraysAsList() {
-    delayTestFinish(TEST_DELAY);
-
-    CollectionsTestServiceAsync service = getServiceAsync();
-    final List expected = new ArrayList();
-    for (byte i = 0; i < 10; ++i) {
-      expected.add(new Byte(i));
-    }
-
-    service.getArraysAsList(expected, new AsyncCallback() {
-      public void onFailure(Throwable caught) {
-        assertTrue(caught.getClass().getName()
-            + " should have been an InvocationException",
-            caught instanceof InvocationException);
-        finishTest();
-      }
-
-      public void onSuccess(Object result) {
-        fail("Expected an InvocationException");
-      }
-    });
-  }
-
   public void testFloatArray() {
     delayTestFinish(TEST_DELAY);
 
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 02ec4eb..114f2c8 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
@@ -98,5 +98,6 @@
   Vector<IsSerializable> echo(Vector<IsSerializable> value)
       throws CollectionsTestServiceException;
 
-  List<IsSerializable> getArraysAsList(List<IsSerializable> value);
+  List<IsSerializable> echoArraysAsList(List<IsSerializable> 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 23e6081..1a4d331 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
@@ -84,6 +84,6 @@
   void echo(Vector<IsSerializable> value,
       AsyncCallback<Vector<IsSerializable>> callback);
 
-  void getArraysAsList(List<IsSerializable> value,
+  void echoArraysAsList(List<IsSerializable> value,
       AsyncCallback<List<IsSerializable>> 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 1b99843..cbe483d 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
@@ -18,9 +18,11 @@
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Vector;
 
 /**
@@ -397,6 +399,10 @@
     return list;
   }
 
+  static List createArraysAsList() {
+    return Arrays.asList((byte) 0, (byte) 1, (byte) 2, (byte) 3);
+  }
+
   static SerializableDoublyLinkedNode createComplexCyclicGraph() {
     SerializableDoublyLinkedNode n1 = new SerializableDoublyLinkedNode();
     n1.setData("n0");
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 188ba01..dd663fb 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -24,6 +24,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Vector;
@@ -305,7 +306,7 @@
 
     return actual.getValue() == 1;
   }
-  
+
   /**
    * We want to assert that the two fields have object identity.
    */
@@ -359,6 +360,19 @@
     return true;
   }
 
+  public static boolean isValidAsList(List list) {
+    if (list == null) {
+      return false;
+    }
+
+    List reference = TestSetFactory.createArraysAsList();
+    if (reference.size() != list.size()) {
+      return false;
+    }
+
+    return reference.equals(list);
+  }
+
   public static boolean isValidComplexCyclicGraph(
       SerializableDoublyLinkedNode actual) {
 
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 f7c2337..c002e2e 100644
--- a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
@@ -312,12 +312,12 @@
    * InvocationException on the client.
    */
   @SuppressWarnings("unchecked")
-  public List getArraysAsList(List value) {
-    Byte[] retVal = new Byte[10];
-    for (byte i = 0; i < 10; ++i) {
-      retVal[i] = (Byte) value.get(i);
+  public List echoArraysAsList(List value)
+      throws CollectionsTestServiceException {
+    if (!TestSetValidator.isValidAsList(value)) {
+      throw new CollectionsTestServiceException();
     }
 
-    return Arrays.asList(retVal);
+    return value;
   }
 }