From a4876c2a6f7743b9d3d069e9a37ef896601082f2 Mon Sep 17 00:00:00 2001 From: Joshua Barr Date: Tue, 14 Oct 2025 16:00:01 -0700 Subject: [PATCH 1/4] Document symmetry-breaking equals in IonSequence Fixes #1121 --- src/main/java/com/amazon/ion/IonSequence.java | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/amazon/ion/IonSequence.java b/src/main/java/com/amazon/ion/IonSequence.java index 63995fcaea..dd1bdb7a46 100644 --- a/src/main/java/com/amazon/ion/IonSequence.java +++ b/src/main/java/com/amazon/ion/IonSequence.java @@ -426,15 +426,15 @@ public void add(int index, IonValue child) *

* * The implementation of {@link List} returned by this method - * implements {@link List#equals(Object)} and - * {@link List#equals(Object)} ()} per the specification of these methods. + * implements {@link List#equals} and {@link List#hashCode()} + * 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: + * {@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)
@@ -444,7 +444,7 @@ public void add(int index, IonValue child)
      * List dgrmSubList = sexp.subList(0, ints.size())
      * List arrayList = new ArrayList();
      * for(int i : ints) { arrayList.add(SYSTEM.newInt(i)); }
-     * 
+     * } 
* * {@link IonSequence#equals(Object)} always returns false when presented * with a non {@link IonSequence} instance of {@link List}. @@ -457,7 +457,7 @@ public void add(int index, IonValue child) * library we maintain backwards compatibility and support this behaviour * as-is. * - * + *
 {@code
      * list.equals(listSubList)     // false
      * list.equals(sexpSubList)     // false
      * list.equals(dgrm)            // false
@@ -472,7 +472,7 @@ public void add(int index, IonValue child)
      * dgrm.equals(sexpSubList)     // false
      * dgrm.equals(dgrmSubList)     // false
      * dgrm.equals(arrayList)       // false
-     *
+     * } 
* * However, {@link IonSequence#subList(int, int)} was implemented much * later and faithfully implements {@link List#equals(Object)} meaning @@ -483,7 +483,7 @@ public void add(int index, IonValue child) * 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
@@ -504,7 +504,7 @@ public void add(int index, IonValue child)
      * dgrmSubList.equals(list);        // true
      * dgrmSubList.equals(sexp);        // true
      * dgrmSubList.equals(arrayList);   // true
-     * 
+     * } 
* * @see List#subList(int, int) */ @@ -566,4 +566,60 @@ public void add(int index, IonValue child) public IonSequence clone() throws UnknownSymbolException; + + /** + * The existing implementation of {@link IonSequence} does not + * provide a specification-compliant {@link List#equals} and + * {@link List#hashCode()}. If you want a symmetric {@code equals} then + * use a {@link IonSequence#subList(int, int)} view of the sequence. + *

+ * The non-compliant {@code equals} implementation of {@link IonSequence} + * 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 listSubList = list.subList(0, ints.size())
+     * List sexpSubList = sexp.subList(0, ints.size())
+     * List dgrmSubList = sexp.subList(0, ints.size())
+     * List arrayList = new ArrayList();
+     * for(int i : ints) { arrayList.add(SYSTEM.newInt(i)); }
+     * } 
+ * + * {@link IonSequence#equals(Object)} always returns false when presented + * with a non {@link IonSequence} instance of {@link List}. + * 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
+     * } 
+ * + * @param other the object to be compared for equality with this list + * @return {@code true} if the specified object is equal to this list + */ + boolean equals(Object other); } From c6f123501dc47ff44179a17f55d1d42fc0abac14 Mon Sep 17 00:00:00 2001 From: Joshua Barr <70981087+jobarr-amzn@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:16:41 -0700 Subject: [PATCH 2/4] Update src/main/java/com/amazon/ion/IonSequence.java Co-authored-by: Austin Williams --- src/main/java/com/amazon/ion/IonSequence.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/amazon/ion/IonSequence.java b/src/main/java/com/amazon/ion/IonSequence.java index dd1bdb7a46..10b71de9f9 100644 --- a/src/main/java/com/amazon/ion/IonSequence.java +++ b/src/main/java/com/amazon/ion/IonSequence.java @@ -585,7 +585,7 @@ public IonSequence clone() * IonSexp dgrm = SYSTEM.newDatagram(ints) * List listSubList = list.subList(0, ints.size()) * List sexpSubList = sexp.subList(0, ints.size()) - * List dgrmSubList = sexp.subList(0, ints.size()) + * List dgrmSubList = dgrm.subList(0, ints.size()) * List arrayList = new ArrayList(); * for(int i : ints) { arrayList.add(SYSTEM.newInt(i)); } * } From 46782f8fc295f72690975cce22bea395348c46a7 Mon Sep 17 00:00:00 2001 From: Joshua Barr Date: Thu, 16 Oct 2025 09:19:38 -0700 Subject: [PATCH 3/4] Further copypasta fixes --- src/main/java/com/amazon/ion/IonSequence.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/amazon/ion/IonSequence.java b/src/main/java/com/amazon/ion/IonSequence.java index 10b71de9f9..e322566eed 100644 --- a/src/main/java/com/amazon/ion/IonSequence.java +++ b/src/main/java/com/amazon/ion/IonSequence.java @@ -438,10 +438,10 @@ public void add(int index, IonValue child) * int[] ints = new int[] {1, 2, 3, 4}; * IonList list = SYSTEM.newList(ints); * IonSexp sexp = SYSTEM.newSexp(ints) - * IonSexp dgrm = SYSTEM.newDatagram(ints) + * IonDatagram dgrm = SYSTEM.newDatagram(ints) * List listSubList = list.subList(0, ints.size()) * List sexpSubList = sexp.subList(0, ints.size()) - * List dgrmSubList = sexp.subList(0, ints.size()) + * List dgrmSubList = dgrm.subList(0, ints.size()) * List arrayList = new ArrayList(); * for(int i : ints) { arrayList.add(SYSTEM.newInt(i)); } * } @@ -582,7 +582,7 @@ public IonSequence clone() * int[] ints = new int[] {1, 2, 3, 4}; * IonList list = SYSTEM.newList(ints); * IonSexp sexp = SYSTEM.newSexp(ints) - * IonSexp dgrm = SYSTEM.newDatagram(ints) + * IonDatagram dgrm = SYSTEM.newDatagram(ints) * List listSubList = list.subList(0, ints.size()) * List sexpSubList = sexp.subList(0, ints.size()) * List dgrmSubList = dgrm.subList(0, ints.size()) @@ -604,12 +604,12 @@ public IonSequence clone() *
 {@code
      * list.equals(listSubList)     // false
      * list.equals(sexpSubList)     // false
-     * list.equals(dgrm)            // false
+     * list.equals(dgrmSubList)     // false
      * list.equals(arrayList)       // false
      *
      * sexp.equals(listSubList)     // false
      * sexp.equals(sexpSubList)     // false
-     * sexp.equals(dgrm)            // false
+     * sexp.equals(dgrmSubList)     // false
      * sexp.equals(arrayList)       // false
      *
      * dgrm.equals(listSubList)     // false

From 0026d0aee76fca80d7180e5b68722dc89d59dbec Mon Sep 17 00:00:00 2001
From: Joshua Barr <70981087+jobarr-amzn@users.noreply.github.com>
Date: Thu, 16 Oct 2025 09:59:55 -0700
Subject: [PATCH 4/4] Update src/main/java/com/amazon/ion/IonSequence.java

Co-authored-by: Austin Williams 
---
 src/main/java/com/amazon/ion/IonSequence.java | 39 ++++++++++---------
 1 file changed, 20 insertions(+), 19 deletions(-)

diff --git a/src/main/java/com/amazon/ion/IonSequence.java b/src/main/java/com/amazon/ion/IonSequence.java
index e322566eed..a2fc70d400 100644
--- a/src/main/java/com/amazon/ion/IonSequence.java
+++ b/src/main/java/com/amazon/ion/IonSequence.java
@@ -581,11 +581,12 @@ public IonSequence clone()
      * 
 {@code
      * int[] ints = new int[] {1, 2, 3, 4};
      * IonList list = SYSTEM.newList(ints);
-     * IonSexp sexp = SYSTEM.newSexp(ints)
-     * IonDatagram dgrm = SYSTEM.newDatagram(ints)
-     * List listSubList = list.subList(0, ints.size())
-     * List sexpSubList = sexp.subList(0, ints.size())
-     * List dgrmSubList = dgrm.subList(0, ints.size())
+     * IonSexp sexp = SYSTEM.newSexp(ints);
+     * IonDatagram dgrm = SYSTEM.newDatagram();
+     * for(int i : ints) { dgrm.add(SYSTEM.newInt(i)); };
+     * List listSubList = list.subList(0, ints.length);
+     * List sexpSubList = sexp.subList(0, ints.length);
+     * List dgrmSubList = dgrm.subList(0, ints.length);
      * List arrayList = new ArrayList();
      * for(int i : ints) { arrayList.add(SYSTEM.newInt(i)); }
      * } 
@@ -602,20 +603,20 @@ public IonSequence clone() * as-is. * *
 {@code
-     * list.equals(listSubList)     // false
-     * list.equals(sexpSubList)     // false
-     * list.equals(dgrmSubList)     // false
-     * list.equals(arrayList)       // false
-     *
-     * sexp.equals(listSubList)     // false
-     * sexp.equals(sexpSubList)     // false
-     * sexp.equals(dgrmSubList)     // false
-     * sexp.equals(arrayList)       // false
-     *
-     * dgrm.equals(listSubList)     // false
-     * dgrm.equals(sexpSubList)     // false
-     * dgrm.equals(dgrmSubList)     // false
-     * dgrm.equals(arrayList)       // false
+     * list.equals(listSubList);     // false
+     * list.equals(sexpSubList);     // false
+     * list.equals(dgrmSubList);     // false
+     * list.equals(arrayList);       // false
+     *
+     * sexp.equals(listSubList);     // false
+     * sexp.equals(sexpSubList);     // false
+     * sexp.equals(dgrmSubList);     // false
+     * sexp.equals(arrayList);       // false
+     *
+     * dgrm.equals(listSubList);     // false
+     * dgrm.equals(sexpSubList);     // false
+     * dgrm.equals(dgrmSubList);     // false
+     * dgrm.equals(arrayList);       // false
      * } 
* * @param other the object to be compared for equality with this list