Repository: amzn/ion-java
Pull request #297 (2020-06-05 20:52:10)

Make IonSequence.SubListView implement equals and hashCode
By changing `IonSequence.SubListView` to extend [`AbstractList`](https://docs.oracle.com/javase/7/docs/api/java/util/AbstractList.html) we inherit its implementation of `.equals` and `.hashCode`.

The only real work here was the unit tests and the javadoc.

Also adds `JUnitParams` as a test dependency since (IMHO) this provides a much better method of test parameterization.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


codecov[bot]: # [Codecov](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=h1) Report
> Merging [#297](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=desc) into [master](https://codecov.io/gh/amzn/ion-java/commit/b3115b27ee572e5d14af261dea445f72b8522d06&el=desc) will **increase** coverage by `0.00%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/amzn/ion-java/pull/297/graphs/tree.svg?width=650&height=150&src=pr&token=M93Y3e3kKn)](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##             master     #297   +/-   ##
=========================================
  Coverage     63.90%   63.91%           
- Complexity     4805     4807    +2     
=========================================
  Files           136      136           
  Lines         21062    21062           
  Branches       3823     3823           
=========================================
+ Hits          13460    13461    +1     
  Misses         6258     6258           
+ Partials       1344     1343    -1     
```


| [Impacted Files](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [src/com/amazon/ion/impl/lite/IonSequenceLite.java](https://codecov.io/gh/amzn/ion-java/pull/297/diff?src=pr&el=tree#diff-c3JjL2NvbS9hbWF6b24vaW9uL2ltcGwvbGl0ZS9Jb25TZXF1ZW5jZUxpdGUuamF2YQ==) | `84.06% <ø> (ø)` | `56.00 <0.00> (ø)` | |
| [src/com/amazon/ion/impl/BlockedBuffer.java](https://codecov.io/gh/amzn/ion-java/pull/297/diff?src=pr&el=tree#diff-c3JjL2NvbS9hbWF6b24vaW9uL2ltcGwvQmxvY2tlZEJ1ZmZlci5qYXZh) | `51.21% <0.00%> (+0.12%)` | `79.00% <0.00%> (+2.00%)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=footer). Last update [b3115b2...71c879e](https://codecov.io/gh/amzn/ion-java/pull/297?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/pom.xml b/pom.xml
index bab0e920..62ac58ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,12 @@
       <version>4.12</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>pl.pragmatists</groupId>
+      <artifactId>JUnitParams</artifactId>
+      <version>1.1.1</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/src/com/amazon/ion/IonSequence.java b/src/com/amazon/ion/IonSequence.java
index 9621859e..63995fca 100644
--- a/src/com/amazon/ion/IonSequence.java
+++ b/src/com/amazon/ion/IonSequence.java
@@ -44,10 +44,24 @@ import java.util.ListIterator;
  *     non-Ion {@link Collection}s.
  *   </li>
  *   <li>
- *     The method {@link #subList(int, int)} is not implemented at all.
- *     We think it will be quite challenging to get correct, and decided that
- *     it was still valuable to extend {@link List} even with this contractual
- *     violation.
+ *     The implementations of {@link List#equals(Object)}} and
+ *     {@link List#hashCode()} does not conform to the specification of those
+ *     methods in order to conform with Ion's definition of equality which
+ *     takes into account the {@link IonSequence}'s {@link IonType} and its
+ *     annotations in addition to the contents of the collection.
+ *     <ul>
+ *       <li>
+ *         {@link List#equals(Object)} always returns <code>false</code>
+ *         for any non-{@link IonSequence} implementation of {@link List},
+ *         including the sub-list returned from
+ *         {@link IonSequence#subList(int, int)}.
+ *       </li>
+ *       <li>
+ *         {@link List#hashCode()} returns a different hash code than
+ *         other {@link List#hashCode()} implementations. including the
+ *         sub-list returned from {@link IonSequence#subList(int, int)}.
+ *       </li>
+ *     </ul>
  *   </li>
  * </ul>
  */
@@ -400,16 +414,98 @@ public interface IonSequence
 
     /**
      * <p>
-     * Returns a view of the portion of this list according to {@link List#subList(int, int)}
-     * contract.
+     * Returns a view of the portion of this list according to
+     * {@link List#subList(int, int)} contract.
      * </p>
      *
      * <p>
-     * Sublist methods will throw a {@link java.util.ConcurrentModificationException} if
-     * its parent list, i.e. this list, had any changes that affect its size the after sublist
+     * Sublist methods will throw a
+     * {@link java.util.ConcurrentModificationException} if its parent list,
+     * i.e. this list, had any changes that affect its size the after sublist
      * was created.
      * </p>
      *
+     * The implementation of {@link List<IonValue>} returned by this method
+     * implements {@link List#equals(Object)} and
+     * {@link List#equals(Object)} ()} per the specification of these methods.
+     * However, the existing implementation of {@link IonSequence} does not
+     * provide a specification compliant {@link List#equals} and
+     * {@link List#hashCode()}} which results to the following caveats:
+     *
+     * Given:
+     *
+     * <code>
+     * int[] ints = new int[] {1, 2, 3, 4};
+     * IonList list = SYSTEM.newList(ints);
+     * IonSexp sexp = SYSTEM.newSexp(ints)
+     * IonSexp dgrm = SYSTEM.newDatagram(ints)
+     * List<IonValue> listSubList = list.subList(0, ints.size())
+     * List<IonValue> sexpSubList = sexp.subList(0, ints.size())
+     * List<IonValue> dgrmSubList = sexp.subList(0, ints.size())
+     * List<IonValue> arrayList = new ArrayList<IonValue>();
+     * for(int i : ints) { arrayList.add(SYSTEM.newInt(i)); }
+     * </code>
+     *
+     * {@link IonSequence#equals(Object)} always returns false when presented
+     * with a non {@link IonSequence} instance of {@link List<IonValue>}.
+     * Hence, the following invocations of {@link Object#equals(Object)}
+     * return false even if the contained elements are equivalent.  This
+     * means that {@link Object#equals(Object)} is not symmetric in these
+     * cases.  The reason for the asymmetry is historical:
+     * {@link IonSequence} has long violated the contract outlined by the
+     * {@link List} documentation.  For the current major version of this
+     * library we maintain backwards compatibility and support this behaviour
+     * as-is.
+     *
+     * <code>
+     * list.equals(listSubList)     // false
+     * list.equals(sexpSubList)     // false
+     * list.equals(dgrm)            // false
+     * list.equals(arrayList)       // false
+     *
+     * sexp.equals(listSubList)     // false
+     * sexp.equals(sexpSubList)     // false
+     * sexp.equals(dgrm)            // false
+     * sexp.equals(arrayList)       // false
+     *
+     * dgrm.equals(listSubList)     // false
+     * dgrm.equals(sexpSubList)     // false
+     * dgrm.equals(dgrmSubList)     // false
+     * dgrm.equals(arrayList)       // false
+     *</code>
+     *
+     * However, {@link IonSequence#subList(int, int)} was implemented much
+     * later and faithfully implements {@link List#equals(Object)} meaning
+     * the cases below all work as expected.  While {@link IonSequence}
+     * does not comply with the specification for {@link List#equals(Object)}
+     * because it has to comply with Ion's rules for equality, the same is
+     * not true for sub-lists.  Unlike {@link IonSequence}, sub-lists have
+     * no notion of an {@link IonType}, annotations or nullability which
+     * allows for compliance with the {@link List} specification.
+     *
+     * <code>
+     * listSubList.equals(listSubList); // true
+     * listSubList.equals(sexpSubList); // true
+     * listSubList.equals(dgrmSubList); // true
+     * listSubList.equals(list);        // true
+     * listSubList.equals(sexp);        // true
+     * listSubList.equals(arrayList);   // true
+     *
+     * sexpSubList.equals(listSubList); // true
+     * sexpSubList.equals(sexpSubList); // true
+     * sexpSubList.equals(dgrmSubList); // true
+     * sexpSubList.equals(list);        // true
+     * sexpSubList.equals(sexp);        // true
+     * sexpSubList.equals(arrayList);   // true
+     *
+     * dgrmSubList.equals(listSubList); // true
+     * dgrmSubList.equals(sexpSubList); // true
+     * dgrmSubList.equals(dgrmSubList); // true
+     * dgrmSubList.equals(list);        // true
+     * dgrmSubList.equals(sexp);        // true
+     * dgrmSubList.equals(arrayList);   // true
+     * </code>
+     *
      * @see List#subList(int, int)
      */
     public List<IonValue> subList(int fromIndex, int toIndex);
diff --git a/src/com/amazon/ion/impl/lite/IonSequenceLite.java b/src/com/amazon/ion/impl/lite/IonSequenceLite.java
index d7e2eeeb..d8ae7ac3 100644
--- a/src/com/amazon/ion/impl/lite/IonSequenceLite.java
+++ b/src/com/amazon/ion/impl/lite/IonSequenceLite.java
@@ -25,6 +25,7 @@ import com.amazon.ion.impl._Private_CurriedValueFactory;
 import com.amazon.ion.impl._Private_IonValue;
 import java.io.IOException;
 import java.lang.reflect.Array;
+import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.ConcurrentModificationException;
@@ -429,7 +430,7 @@ abstract class IonSequenceLite
      *
      * Structural modifications from the sublist itself are allowed.
      */
-    private class SubListView implements List<IonValue> {
+    private class SubListView extends AbstractList<IonValue> {
 
         /**
          * index in top level IonSequenceLite that marks the start of this sublist view. For nested
@@ -437,24 +438,26 @@ abstract class IonSequenceLite
          */
         private final int fromIndex;
         private int size;
-        private int structuralModificationCount;
 
         private SubListView(final int fromIndex, final int toIndex) {
             this.fromIndex = fromIndex;
             this.size = toIndex - fromIndex;
-            this.structuralModificationCount = IonSequenceLite.this.structuralModificationCount;
+            super.modCount = IonSequenceLite.this.structuralModificationCount;
         }
 
+        @Override
         public int size() {
             checkForParentModification();
             return size;
         }
 
+        @Override
         public boolean isEmpty() {
             checkForParentModification();
             return size == 0;
         }
 
+        @Override
         public IonValue get(final int index) {
             checkForParentModification();
             rangeCheck(index);
@@ -462,6 +465,7 @@ abstract class IonSequenceLite
             return IonSequenceLite.this.get(toParentIndex(index));
         }
 
+        @Override
         public IonValue set(final int index, final IonValue element) {
             checkForParentModification();
             rangeCheck(index);
@@ -469,53 +473,31 @@ abstract class IonSequenceLite
             return IonSequenceLite.this.set(toParentIndex(index), element);
         }
 
+        @Override
         public boolean contains(final Object o) {
             checkForParentModification();
-            return indexOf(o) != -1;
+            return super.contains(o);
         }
 
-        public boolean containsAll(final Collection<?> c) {
-            for (Object o : c) {
-                if (!contains(o)) {
-                    return false;
-                }
-            }
-
-            return true;
+        @Override
+        public boolean containsAll(final Collection<?> collection) {
+            checkForParentModification();
+            return super.containsAll(collection);
         }
 
-        public IonValue[] toArray() {
+        @Override
+        public Object[] toArray() {
             checkForParentModification();
-
-            if (size < 1) {
-                return EMPTY_VALUE_ARRAY;
-            }
-
-            return toArray(new IonValue[size]);
+            return super.toArray();
         }
 
-        @SuppressWarnings("unchecked")
-        public <T> T[] toArray(T[] array) {
+        @Override
+        public <T> T[] toArray(T[] ts) {
             checkForParentModification();
-
-            if (array.length < size) {
-                final Class<?> type = array.getClass().getComponentType();
-                // generates unchecked warning
-                array = (T[]) Array.newInstance(type, size);
-            }
-
-            if (size > 0) {
-                System.arraycopy(IonSequenceLite.this._children, fromIndex, array, 0, size);
-            }
-
-            if (size < array.length) {
-                // See IonSequence#toArray and ArrayList#toArray
-                array[size] = null;
-            }
-
-            return array;
+            return super.toArray(ts);
         }
 
+        @Override
         public boolean add(final IonValue ionValue) {
             checkForParentModification();
 
@@ -528,42 +510,42 @@ abstract class IonSequenceLite
                 IonSequenceLite.this.add(parentIndex, ionValue);
             }
 
-            this.structuralModificationCount = IonSequenceLite.this.structuralModificationCount;
+            super.modCount = IonSequenceLite.this.structuralModificationCount;
             size++;
 
             return true;
         }
 
+        @Override
         public void add(final int index, final IonValue ionValue) {
             checkForParentModification();
             rangeCheck(index);
 
             IonSequenceLite.this.add(toParentIndex(index), ionValue);
 
-            this.structuralModificationCount = IonSequenceLite.this.structuralModificationCount;
+            super.modCount = IonSequenceLite.this.structuralModificationCount;
             size++;
         }
 
-        public boolean addAll(final Collection<? extends IonValue> c) {
-            for (IonValue ionValue : c) {
-                add(ionValue);
-            }
 
-            return true;
+        @Override
+        public boolean addAll(int i, Collection<? extends IonValue> collection) {
+            checkForParentModification();
+            return super.addAll(i, collection);
         }
 
-        public boolean addAll(final int index, final Collection<? extends IonValue> c) {
-            int i = index;
 
-            for (IonValue ionValue : c) {
-                add(i, ionValue);
-                i++;
-            }
-
-            return true;
+        @Override
+        public boolean addAll(Collection<? extends IonValue> collection) {
+            checkForParentModification();
+            return super.addAll(collection);
         }
 
+        // retainAll has no functionality that is specific to SubListView but
+        // needs to be implemented because the AbstractList<T>.retainAll() throws [UnsupportedOperationException].
+        @Override
         public boolean retainAll(final Collection<?> c) {
+            checkForParentModification();
             if (size < 1) {
                 return false;
             }
@@ -585,6 +567,7 @@ abstract class IonSequenceLite
             return removeAll(toRemove);
         }
 
+        @Override
         public void clear() {
             checkForParentModification();
 
@@ -595,22 +578,27 @@ abstract class IonSequenceLite
             }
 
             size = 0;
-            this.structuralModificationCount = IonSequenceLite.this.structuralModificationCount;
+            super.modCount = IonSequenceLite.this.structuralModificationCount;
         }
 
+        @Override
         public IonValue remove(final int index) {
             checkForParentModification();
             rangeCheck(index);
 
             final IonValue removed = IonSequenceLite.this.remove(toParentIndex(index));
 
-            this.structuralModificationCount = IonSequenceLite.this.structuralModificationCount;
+            super.modCount = IonSequenceLite.this.structuralModificationCount;
             size--;
 
             return removed;
         }
 
+        // remove(Object) contains no functionality that is specific to SubListView but
+        // needs to be implemented because the AbstractList<T>.remove(Object) throws [UnsupportedOperationException].
+        @Override
         public boolean remove(final Object o) {
+            checkForParentModification();
             int index = indexOf(o);
             if (index < 0) {
                 return false;
@@ -620,7 +608,11 @@ abstract class IonSequenceLite
             return true;
         }
 
+        // removeAll(Collection<?>) contains no functionality that is specific to SubListView but
+        // needs to be implemented because the AbstractList<T>.remove(Object) throws [UnsupportedOperationException].
+        @Override
         public boolean removeAll(final Collection<?> c) {
+            checkForParentModification();
             boolean changed = false;
             for (Object o : c) {
                 if (remove(o)) {
@@ -631,32 +623,36 @@ abstract class IonSequenceLite
             return changed;
         }
 
+        @Override
         public int indexOf(final Object o) {
             checkForParentModification();
+            return super.indexOf(o);
+        }
 
-            final int parentIndex = IonSequenceLite.this.indexOf(o);
-            final int index = fromParentIndex(parentIndex);
-
-            // not found
-            if (parentIndex < 0 || index < 0 || index >= size) {
-                return -1;
-            }
-
-            return index;
+        @Override
+        public int lastIndexOf(Object o) {
+            checkForParentModification();
+            return super.lastIndexOf(o);
         }
 
-        public int lastIndexOf(final Object o) {
-            return indexOf(o);
+        @Override
+        protected void removeRange(int from, int to) {
+            checkForParentModification();
+            super.removeRange(from, to);
         }
 
+
+        @Override
         public Iterator<IonValue> iterator() {
             return listIterator(0);
         }
 
+        @Override
         public ListIterator<IonValue> listIterator() {
             return listIterator(0);
         }
 
+        @Override
         public ListIterator<IonValue> listIterator(final int index) {
             checkForParentModification();
 
@@ -704,12 +700,31 @@ abstract class IonSequenceLite
             };
         }
 
+        @Override
         public List<IonValue> subList(final int fromIndex, final int toIndex) {
             checkSublistParameters(this.size(), fromIndex, toIndex);
             checkForParentModification();
             return new SubListView(toParentIndex(fromIndex), toParentIndex(toIndex));
         }
 
+        @Override
+        public boolean equals(Object o) {
+            checkForParentModification();
+            return super.equals(o);
+        }
+
+        @Override
+        public int hashCode() {
+            checkForParentModification();
+            return super.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            checkForParentModification();
+            return super.toString();
+        }
+
         private void rangeCheck(int index) {
             if (index < 0 || index >= size) {
                 throw new IndexOutOfBoundsException(String.valueOf(index));
@@ -720,12 +735,8 @@ abstract class IonSequenceLite
             return index + fromIndex;
         }
 
-        private int fromParentIndex(int index) {
-            return index - fromIndex;
-        }
-
         private void checkForParentModification() {
-            if (this.structuralModificationCount != IonSequenceLite.this.structuralModificationCount) {
+            if (super.modCount != IonSequenceLite.this.structuralModificationCount) {
                 throw new ConcurrentModificationException();
             }
         }


Test patch:
diff --git a/test/com/amazon/ion/impl/lite/BaseIonSequenceLiteSublistTestCase.java b/test/com/amazon/ion/impl/lite/BaseIonSequenceLiteSublistTestCase.java
index 13562eef..67b60f8f 100644
--- a/test/com/amazon/ion/impl/lite/BaseIonSequenceLiteSublistTestCase.java
+++ b/test/com/amazon/ion/impl/lite/BaseIonSequenceLiteSublistTestCase.java
@@ -15,16 +15,13 @@
 
 package com.amazon.ion.impl.lite;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.ConcurrentModificationException;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
+
 import org.junit.Test;
 import com.amazon.ion.ContainedValueException;
 import com.amazon.ion.IonInt;
@@ -33,6 +30,8 @@ import com.amazon.ion.IonSystem;
 import com.amazon.ion.IonValue;
 import com.amazon.ion.system.IonSystemBuilder;
 
+import static org.junit.Assert.*;
+
 /**
  * All tests related to {@link IonSequenceLite#subList(int, int)}. Extracted to a separate test due to the amount of
  * tests
diff --git a/test/com/amazon/ion/impl/lite/IonSequenceLiteSubListViewEqualityTests.java b/test/com/amazon/ion/impl/lite/IonSequenceLiteSubListViewEqualityTests.java
new file mode 100644
index 00000000..292b4a7b
--- /dev/null
+++ b/test/com/amazon/ion/impl/lite/IonSequenceLiteSubListViewEqualityTests.java
@@ -0,0 +1,180 @@
+package com.amazon.ion.impl.lite;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.amazon.ion.IonSequence;
+import com.amazon.ion.IonSystem;
+import com.amazon.ion.IonValue;
+import com.amazon.ion.system.IonSystemBuilder;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+@RunWith(JUnitParamsRunner.class)
+public class IonSequenceLiteSubListViewEqualityTests {
+    static final IonSystem SYSTEM = IonSystemBuilder.standard().build();
+    private static List<Integer> INTS = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
+    private static List<Integer> OTHER_INTS = Arrays.asList(0, 1, 2, 33, 4, 5, 6);
+    private static List<IonValue> ARRAY_LIST = newArrayList(INTS);
+    private static List<IonValue> OTHER_ARRAY_LIST = newArrayList(OTHER_INTS);
+
+    private interface SublistMaker {
+        List<IonValue> makeSublist(List<IonValue> seq);
+    }
+
+    private static void runSubListEquivalenceTests(List<IonValue> seq, SublistMaker maker) {
+        testSublistEquivalence(seq, maker);
+        testSublistNonEquivalence(seq, maker);
+    }
+
+    // makes a sublist of seq and asserts its equivalence to sublists of all sequence types and to a sublist
+    // derived from ARRAY_LIST
+    private static void testSublistEquivalence(List<IonValue> seq, SublistMaker maker) {
+        List<IonValue> subList = maker.makeSublist(seq);
+        for(IonSequence otherSeq : getTestSequences()) {
+            List<IonValue> otherSubList = maker.makeSublist(otherSeq);
+            assertEquals("subLists should be equivalent", subList, otherSubList);
+            assertEquals("subLists should have the same hashCode", subList.hashCode(), otherSubList.hashCode());
+        }
+
+        List<IonValue> ararySubList = maker.makeSublist(ARRAY_LIST);
+        assertEquals("subList should be equivalent to a sublist of ARRAY_LIST",
+                ararySubList, subList);
+
+        assertEquals("subList should have the same hash code as a sublist of ARRAY_LIST",
+                ararySubList.hashCode(), subList.hashCode());
+    }
+
+    // makes a sublist of seq and asserts its non-equivalence to the same subList range of the "other" sequence
+    // and to a sublist derived from OTHER_ARRAY_LIST
+    private static void testSublistNonEquivalence(List<IonValue> seq, SublistMaker maker) {
+        // A hash collision is unlikely but possible so we do not assert that the hashCodes are different here.
+
+        List<IonValue> subList = maker.makeSublist(seq);
+        for(IonSequence otherSeq : getOtherTestSequences()) {
+            List<IonValue> otherSubList = maker.makeSublist(otherSeq);
+            assertNotEquals("subLists should *not* be equivalent", subList, otherSubList);
+        }
+
+        assertNotEquals("subList should *not* be equivalent to a sublist of OTHER_ARRAY_LIST",
+                maker.makeSublist(OTHER_ARRAY_LIST), subList);
+    }
+
+    private static IonSequence[] getParameters(List<Integer> ints) {
+        return new IonSequence[] {
+            populateSequence(SYSTEM.newList(), ints),
+            populateSequence(SYSTEM.newSexp(), ints),
+            populateSequence(SYSTEM.newDatagram(), ints)
+        };
+    }
+
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void fullSubList(IonSequence seq) {
+        SublistMaker maker = new SublistMaker() {
+            @Override
+            public List<IonValue> makeSublist(List<IonValue> seq) {
+                return seq.subList(0, seq.size());
+            }
+        };
+        runSubListEquivalenceTests(seq, maker);
+
+        // The sublist should also be equivalent to the ARRAY_LIST
+        List<IonValue> subList = maker.makeSublist(seq);
+        assertEquals("The full sublist should be equivalent to the ArrayList<IonValue",
+                ARRAY_LIST, subList);
+
+        assertNotEquals("The full sublist should *not* be equivalent to the *other* ArrayList<IonValue",
+                OTHER_ARRAY_LIST, subList);
+    }
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void fullSubListOfFullSubList(IonSequence seq) {
+        SublistMaker maker = new SublistMaker() {
+            @Override
+            public List<IonValue> makeSublist(List<IonValue> seq) {
+                return seq.subList(0, seq.size()).subList(0, seq.size());
+            }
+        };
+        runSubListEquivalenceTests(seq, maker);
+    }
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void partialSubList(IonSequence seq) {
+        SublistMaker maker = new SublistMaker() {
+            @Override
+            public List<IonValue> makeSublist(List<IonValue> seq) {
+                return seq.subList(1, seq.size() - 1);
+            }
+        };
+        runSubListEquivalenceTests(seq, maker);
+    }
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void partialSubListOfPartialSubList(IonSequence seq) {
+        SublistMaker maker = new SublistMaker() {
+            @Override
+            public List<IonValue> makeSublist(List<IonValue> seq) {
+                return seq.subList(1, seq.size() - 1).subList(1, seq.size() - 2);
+            }
+        };
+        runSubListEquivalenceTests(seq, maker);
+    }
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void sequenceEquivalenceToSubList(IonSequence seq) {
+        List<IonValue> subList = seq.subList(0, seq.size());
+
+        // Note the asymmetry.  This is expected. See IonSequence.subList javadoc.
+        assertEquals("the sublist should be equivalent to the sequence", subList, seq);
+        assertNotEquals("the sequence should *not* be equivalent to the subList", seq, subList);
+    }
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void subListNotEquivalentToNull(IonSequence seq) {
+        List<IonValue> subList = seq.subList(0, seq.size());
+        assertNotEquals("subList should not be equivalent to null", subList, null);
+    }
+
+    @Test
+    @Parameters(method = "getTestSequences")
+    public void subListEquivalentToSelf(IonSequence seq) {
+        List<IonValue> subList = seq.subList(0, seq.size());
+        assertEquals("subList should be equivalent to itself", subList, subList);
+    }
+
+    public static IonSequence[] getTestSequences() {
+        return getParameters(INTS);
+    }
+
+    public static IonSequence[] getOtherTestSequences() {
+        return getParameters(OTHER_INTS);
+    }
+
+    private static IonSequence populateSequence(IonSequence seq, List<Integer> ints) {
+        for(int i : ints) {
+            seq.add(SYSTEM.newInt(i));
+        }
+        return seq;
+    }
+
+    private static ArrayList<IonValue> newArrayList(List<Integer> ints) {
+        ArrayList<IonValue> values = new ArrayList<IonValue>();
+        for(int i : ints) {
+            values.add(SYSTEM.newInt(i));
+        }
+        return values;
+    }
+}