diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureCollections.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureCollections.java index 579aaf9b5..1957f4449 100644 --- a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureCollections.java +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureCollections.java @@ -19,7 +19,9 @@ import com.palantir.conjure.java.lib.SafeLong; import com.palantir.logsafe.Preconditions; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -37,6 +39,35 @@ private ConjureCollections() { // cannot instantiate } + /* + * This is bizarre. Allow me to explain... + * + * We do _not_ want to expose the Conjure*List types externally + * but we also want the optimizations they provide to make it thru + * to jackson for serialization. So the runtime type needs to be + * preserved while also not exposing the type :phew:. + * + * To achieve this we have to do some gymnastics surrounding the type + * system. We need this to return the type of the list given, but also + * return specific Conjure types when detected. This requires that we + * erase the type info, but we know this is safe because we are directly + * returning the same type which is by definition the identity function. + * Therefore the input List is the same types as the output List. + */ + public static List unmodifiableList(List list) { + // Return the unmodifiable version of the Eclipse types + if (list instanceof ConjureIntegerList) { + return (List) ((ConjureIntegerList) list).asUnmodifiable(); + } else if (list instanceof ConjureDoubleList) { + return (List) ((ConjureDoubleList) list).asUnmodifiable(); + } else if (list instanceof ConjureSafeLongList) { + return (List) ((ConjureSafeLongList) list).asUnmodifiable(); + } else { + // Otherwise use the JDK types + return Collections.unmodifiableList(list); + } + } + @SuppressWarnings("unchecked") public static void addAll(Collection addTo, Iterable elementsToAdd) { Preconditions.checkNotNull(elementsToAdd, "elementsToAdd cannot be null"); @@ -139,6 +170,12 @@ public static Set newNonNullSet(Iterable iterable) { return set; } + /** + * The following Conjure boxed list wrappers for the eclipse-collections [type]ArrayList are temporary (except + * ConjureSafeLongList). In eclipse-collections 12, a BoxedMutable[type]List will be released. Once available, + * Conjure[type]List should be replaced with that. + */ + // This method returns a list that can't handle nulls. Do not use this unless the nonNullCollections flag is set public static List newNonNullDoubleList() { return new ConjureDoubleList(new DoubleArrayList()); @@ -157,11 +194,14 @@ public static List newNonNullDoubleList(Iterable iterable) { return doubleList; } - /** - * The following Conjure boxed list wrappers for the eclipse-collections [type]ArrayList are temporary (except - * ConjureSafeLongList). In eclipse-collections 12, a BoxedMutable[type]List will be released. Once available, - * Conjure[type]List should be replaced with that. - */ + // This method modifies a list that can't handle nulls. Do not use this unless the nonNullCollections flag is set + public static void addAllToDoubleList(Collection addTo, double[] elementsToAdd) { + if (addTo instanceof ConjureDoubleList) { + ((ConjureDoubleList) addTo).addAll(elementsToAdd); + } else { + addAll(addTo, () -> Arrays.stream(elementsToAdd).iterator()); + } + } // This method returns a list that can't handle nulls. Do not use this unless the nonNullCollections flag is set public static List newNonNullIntegerList() { @@ -181,9 +221,19 @@ public static List newNonNullIntegerList(Iterable iterable) { return integerList; } + // This method modifies a list that can't handle nulls. Do not use this unless the nonNullCollections flag is set + public static void addAllToIntegerList(Collection addTo, int[] elementsToAdd) { + if (addTo instanceof ConjureIntegerList) { + ((ConjureIntegerList) addTo).addAll(elementsToAdd); + } else { + addAll(addTo, () -> Arrays.stream(elementsToAdd).iterator()); + } + } + /** * Deprecated, this should only ever be called by a previously generated conjure internal implementation. */ + // This method returns a list that can't handle nulls. Do not use this unless the nonNullCollections flag is set public static List newNonNullBooleanList() { return newNonNullList(); } @@ -212,4 +262,13 @@ public static List newNonNullSafeLongList(Iterable iterable) return safeLongList; } + + // This method modifies a list that can't handle nulls. Do not use this unless the nonNullCollections flag is set + public static void addAllToSafeLongList(Collection addTo, long[] elementsToAdd) { + if (addTo instanceof ConjureSafeLongList) { + ((ConjureSafeLongList) addTo).addAll(elementsToAdd); + } else { + addAll(addTo, Arrays.stream(elementsToAdd).boxed().map(SafeLong::of).toList()); + } + } } diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureDoubleList.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureDoubleList.java index 58d63bbaa..2e3ac9f5c 100644 --- a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureDoubleList.java +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureDoubleList.java @@ -16,10 +16,11 @@ package com.palantir.conjure.java.lib.internal; +import com.fasterxml.jackson.annotation.JsonValue; import java.util.AbstractList; import java.util.Collection; import java.util.RandomAccess; -import org.eclipse.collections.impl.list.mutable.primitive.DoubleArrayList; +import org.eclipse.collections.api.list.primitive.MutableDoubleList; import org.eclipse.collections.impl.utility.Iterate; /** @@ -27,9 +28,9 @@ * a BoxedMutableDoubleList will be released. Once available, ConjureDoubleList should be replaced with that. */ final class ConjureDoubleList extends AbstractList implements RandomAccess { - private final DoubleArrayList delegate; + private final MutableDoubleList delegate; - ConjureDoubleList(DoubleArrayList delegate) { + ConjureDoubleList(MutableDoubleList delegate) { this.delegate = delegate; } @@ -55,6 +56,10 @@ public boolean addAll(int index, Collection collection) { return delegate.addAllAtIndex(index, target); } + public void addAll(double... source) { + this.delegate.addAll(source); + } + @Override public Double remove(int index) { return delegate.removeAtIndex(index); @@ -69,4 +74,15 @@ public void clear() { public Double set(int index, Double element) { return delegate.set(index, element); } + + public ConjureDoubleList asUnmodifiable() { + return new ConjureDoubleList(delegate.asUnmodifiable()); + } + + // Cannot be named 'toArray' as that conflicts with the #toArray in AbstractList + // This is a serialization optimization that avoids boxing, but does copy + @JsonValue + double[] jacksonSerialize() { + return delegate.toArray(); + } } diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureIntegerList.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureIntegerList.java index 79ecd32b6..48b476fec 100644 --- a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureIntegerList.java +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureIntegerList.java @@ -16,10 +16,11 @@ package com.palantir.conjure.java.lib.internal; +import com.fasterxml.jackson.annotation.JsonValue; import java.util.AbstractList; import java.util.Collection; import java.util.RandomAccess; -import org.eclipse.collections.impl.list.mutable.primitive.IntArrayList; +import org.eclipse.collections.api.list.primitive.MutableIntList; import org.eclipse.collections.impl.utility.Iterate; /** @@ -27,9 +28,9 @@ * a BoxedMutableIntList will be released. Once available, ConjureIntegerList should be replaced with that. */ final class ConjureIntegerList extends AbstractList implements RandomAccess { - private final IntArrayList delegate; + private final MutableIntList delegate; - ConjureIntegerList(IntArrayList delegate) { + ConjureIntegerList(MutableIntList delegate) { this.delegate = delegate; } @@ -55,6 +56,10 @@ public boolean addAll(int index, Collection collection) { return delegate.addAllAtIndex(index, target); } + public void addAll(int... source) { + this.delegate.addAll(source); + } + @Override public Integer remove(int index) { return delegate.removeAtIndex(index); @@ -69,4 +74,15 @@ public void clear() { public Integer set(int index, Integer element) { return delegate.set(index, element); } + + public ConjureIntegerList asUnmodifiable() { + return new ConjureIntegerList(delegate.asUnmodifiable()); + } + + // Cannot be named 'toArray' as that conflicts with the #toArray in AbstractList + // This is a serialization optimization that avoids boxing, but does copy + @JsonValue + int[] jacksonSerialize() { + return delegate.toArray(); + } } diff --git a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureSafeLongList.java b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureSafeLongList.java index 7995d8285..e3d0e50d9 100644 --- a/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureSafeLongList.java +++ b/conjure-lib/src/main/java/com/palantir/conjure/java/lib/internal/ConjureSafeLongList.java @@ -16,11 +16,12 @@ package com.palantir.conjure.java.lib.internal; +import com.fasterxml.jackson.annotation.JsonValue; import com.palantir.conjure.java.lib.SafeLong; import java.util.AbstractList; import java.util.Collection; import java.util.RandomAccess; -import org.eclipse.collections.impl.list.mutable.primitive.LongArrayList; +import org.eclipse.collections.api.list.primitive.MutableLongList; import org.eclipse.collections.impl.utility.Iterate; /** @@ -28,9 +29,9 @@ * with SafeLongs. */ final class ConjureSafeLongList extends AbstractList implements RandomAccess { - private final LongArrayList delegate; + private final MutableLongList delegate; - ConjureSafeLongList(LongArrayList delegate) { + ConjureSafeLongList(MutableLongList delegate) { this.delegate = delegate; } @@ -56,6 +57,14 @@ public boolean addAll(int index, Collection collection) { return delegate.addAllAtIndex(index, target); } + public void addAll(long... source) { + for (long value : source) { + // Doesn't use SafeLong creation because this causes unnecessary boxing + SafeLong.check(value); + } + this.delegate.addAll(source); + } + @Override public SafeLong remove(int index) { return SafeLong.of(delegate.removeAtIndex(index)); @@ -70,4 +79,15 @@ public void clear() { public SafeLong set(int index, SafeLong element) { return SafeLong.of(delegate.set(index, element.longValue())); } + + public ConjureSafeLongList asUnmodifiable() { + return new ConjureSafeLongList(delegate.asUnmodifiable()); + } + + // Cannot be named 'toArray' as that conflicts with the #toArray in AbstractList + // This is a serialization optimization that avoids boxing, but does copy + @JsonValue + long[] jacksonSerialize() { + return delegate.toArray(); + } }