Add List.subList and implementations.

Patch by: wcn@google.com
Review by: jat


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6340 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/super/com/google/gwt/emul/java/util/AbstractList.java b/user/super/com/google/gwt/emul/java/util/AbstractList.java
index 7a08442..e76a993 100644
--- a/user/super/com/google/gwt/emul/java/util/AbstractList.java
+++ b/user/super/com/google/gwt/emul/java/util/AbstractList.java
@@ -110,6 +110,74 @@
     }
   }
 
+  private static class SubList<E> extends AbstractList<E> {
+    private final List<E> wrapped;
+    private final int fromIndex;
+    private int size;
+
+    public SubList(List<E> wrapped, int fromIndex, int toIndex) {
+      this.wrapped = wrapped;
+      this.fromIndex = fromIndex;
+      size = getSize(fromIndex, toIndex);
+      if (fromIndex > toIndex) {
+        throw new IllegalArgumentException("fromIndex: " + fromIndex + 
+            " > toIndex: " + toIndex);
+      }
+      if (fromIndex < 0) {
+        throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + 
+            " < 0");
+      }
+      if (toIndex > wrapped.size()) {
+        throw new IndexOutOfBoundsException("toIndex: " + toIndex +
+            " > wrapped.size() " + wrapped.size());
+      }
+    }
+
+    @Override
+    public void add(int index, E element) {
+      checkIndexForAdd(index);
+      size++;
+      wrapped.add(fromIndex + index, element);   
+    }
+
+    @Override
+    public E get(int index) {
+      checkIndex(index);
+      return wrapped.get(fromIndex + index);
+    }
+
+    @Override
+    public E remove(int index) {
+      checkIndex(index);
+      E result = wrapped.remove(fromIndex + index);
+      size--;
+      return result;
+    }
+
+    @Override
+    public E set(int index, E element) {
+      checkIndex(index);
+      return wrapped.set(fromIndex + index, element);
+    }
+
+    @Override
+    public int size() {
+      return size;
+    }
+    
+    private void checkIndex(int index) {
+      checkIndex(index, size);
+    }
+        
+    private void checkIndexForAdd(int index) {
+      checkIndex(index, size - 1);
+    }
+
+    private int getSize(int fromIndex, int toIndex) {
+      return toIndex - fromIndex;
+    }
+  }
+
   protected static void checkIndex(int index, int size) {
     if (index < 0 || index >= size) {
       indexOutOfBounds(index, size);
@@ -233,9 +301,9 @@
     throw new UnsupportedOperationException("Set not supported on this list");
   }
 
-  // TODO(jat): implement
-//  public List<E> subList(int fromIndex, int toIndex) {
-//  }
+  public List<E> subList(int fromIndex, int toIndex) {
+    return new SubList<E>(this, fromIndex, toIndex);
+  }
 
   protected void removeRange(int fromIndex, int endIndex) {
     ListIterator<E> iter = listIterator(fromIndex);
diff --git a/user/super/com/google/gwt/emul/java/util/ArrayList.java b/user/super/com/google/gwt/emul/java/util/ArrayList.java
index 59b3e77..52a57ed 100644
--- a/user/super/com/google/gwt/emul/java/util/ArrayList.java
+++ b/user/super/com/google/gwt/emul/java/util/ArrayList.java
@@ -276,11 +276,6 @@
     size = newSize;
   }
 
-  // TODO(jat): implement
-//  @Override
-//  List<E> subList(int fromIndex, int toIndex) {
-//  }
-
   @SuppressWarnings("unchecked")
   private void clearImpl() {
     array = (E[]) new Object[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 d5528f2..fdf7ce8 100644
--- a/user/super/com/google/gwt/emul/java/util/Collections.java
+++ b/user/super/com/google/gwt/emul/java/util/Collections.java
@@ -148,6 +148,10 @@
     public T set(int index, T element) {
       throw new UnsupportedOperationException();
     }
+
+    public List<T> subList(int fromIndex, int toIndex) {
+      return new UnmodifiableList<T>(list.subList(fromIndex, toIndex));
+    }
   }
 
   static class UnmodifiableMap<K, V> implements Map<K, V> {
diff --git a/user/super/com/google/gwt/emul/java/util/LinkedList.java b/user/super/com/google/gwt/emul/java/util/LinkedList.java
index 342a11c..f8e280b 100644
--- a/user/super/com/google/gwt/emul/java/util/LinkedList.java
+++ b/user/super/com/google/gwt/emul/java/util/LinkedList.java
@@ -28,6 +28,8 @@
     List<E>, Queue<E>, Serializable {
   /*
    * This implementation uses a doubly-linked circular list with a header node.
+   * 
+   * TODO(jat): add more efficient subList implementation.
    */
 
   /**
@@ -302,11 +304,6 @@
     return size;
   }
 
-  // TODO(jat): implement
-//  @Override
-//  List<E> subList(final int fromIndex, final int toIndex) {
-//  }
-
   private void addBefore(E o, Node<E> target) {
     new Node<E>(o, target);
     ++size;
diff --git a/user/super/com/google/gwt/emul/java/util/List.java b/user/super/com/google/gwt/emul/java/util/List.java
index 10276f3..08831bc 100644
--- a/user/super/com/google/gwt/emul/java/util/List.java
+++ b/user/super/com/google/gwt/emul/java/util/List.java
@@ -67,8 +67,7 @@
 
   int size();
 
-  // TODO(jat): add back when we implement in all List
-  // List<E> subList(int fromIndex, int toIndex);
+  List<E> subList(int fromIndex, int toIndex);
 
   Object[] toArray();
 
diff --git a/user/super/com/google/gwt/emul/java/util/Vector.java b/user/super/com/google/gwt/emul/java/util/Vector.java
index b160320..a0e8748 100644
--- a/user/super/com/google/gwt/emul/java/util/Vector.java
+++ b/user/super/com/google/gwt/emul/java/util/Vector.java
@@ -222,11 +222,10 @@
     return arrayList.size();
   }
 
-  // TODO(jat): add back when ArrayList actually supports subList
-//  @Override
-//  public List<E> subList(int fromIndex, int toIndex) {
-//    return arrayList.subList(fromIndex, toIndex);
-//  }
+  @Override
+  public List<E> subList(int fromIndex, int toIndex) {
+    return arrayList.subList(fromIndex, toIndex);
+  }
 
   @Override
   public Object[] toArray() {
diff --git a/user/test/com/google/gwt/emultest/java/util/ListTestBase.java b/user/test/com/google/gwt/emultest/java/util/ListTestBase.java
index 524fca7..7e2727b 100644
--- a/user/test/com/google/gwt/emultest/java/util/ListTestBase.java
+++ b/user/test/com/google/gwt/emultest/java/util/ListTestBase.java
@@ -17,10 +17,13 @@
 
 import org.apache.commons.collections.TestArrayList;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.ListIterator;
 
+
 /**
  * Tests List, and, by extension AbstractList. Uses inheritance to inherit all
  * of Apache's TestList and TestCollection.
@@ -182,4 +185,101 @@
       }
     }
   }
+
+  public void testSubList() {
+    List<Integer> wrappedList = createListWithContent(new int[]{1, 2, 3, 4, 5});
+    List<Integer> testList = wrappedList.subList(1, 4);
+    assertEquals(3, testList.size());
+    
+    assertEquals(testList, Arrays.asList(2, 3, 4));
+    checkListSizeAndContent(testList, new int[]{2, 3, 4});
+    testList.add(1, 6);
+    assertEquals(testList, Arrays.asList(2, 6, 3, 4));
+    checkListSizeAndContent(testList, new int[]{2, 6, 3, 4});
+    assertEquals(wrappedList, Arrays.asList(1, 2, 6, 3, 4, 5));
+    checkListSizeAndContent(wrappedList, new int[]{1, 2, 6, 3, 4, 5});
+    testList.remove(2);
+    assertEquals(testList, Arrays.asList(2, 6, 4));
+    checkListSizeAndContent(testList, new int[]{2, 6, 4});
+
+    try {
+      testList.remove(3);
+      fail("Expected remove to fail");
+    } catch (IndexOutOfBoundsException e) {
+    }
+
+    checkListSizeAndContent(wrappedList, new int[]{1, 2, 6, 4, 5});
+    testList.set(0, 7);
+    checkListSizeAndContent(testList, new int[]{7, 6, 4});
+    checkListSizeAndContent(wrappedList, new int[]{1, 7, 6, 4, 5}); 
+    
+    try {
+      wrappedList.subList(-1, 5);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+
+    try {
+      wrappedList.subList(0, 15);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+    
+    try {
+      wrappedList.subList(5, 1);
+      fail("expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+    }
+ 
+    try {
+      wrappedList.subList(0, 1).add(2, 5);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+    
+    try {
+      wrappedList.subList(0, 1).add(-1, 5);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+
+    try {
+      wrappedList.subList(0, 1).get(1);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+    
+    try {
+      wrappedList.subList(0, 1).get(-1);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+    
+    try {
+      wrappedList.subList(0, 1).set(2, 2);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+
+    try {
+      wrappedList.subList(0, 1).set(-1, 5);
+      fail("expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+    }
+  }
+  
+  private void checkListSizeAndContent(List<Integer> in, int[] expected) {
+    assertEquals(expected.length, in.size());
+    for (int i = 0; i < expected.length; i++) {
+      assertEquals(expected[i], (int) in.get(i));
+    }
+  }
+  
+  private List<Integer> createListWithContent(int[] in) {
+    List<Integer> results = new ArrayList<Integer>();
+    for (int i = 0; i < in.length; i++) {
+      results.add(in[i]);
+    }
+    return results;
+  }
 }