Skip to content

Instantly share code, notes, and snippets.

@AndrewReitz
Last active December 25, 2018 22:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndrewReitz/48fbff8ef47be3f79424a6cf6bb8f0aa to your computer and use it in GitHub Desktop.
Save AndrewReitz/48fbff8ef47be3f79424a6cf6bb8f0aa to your computer and use it in GitHub Desktop.
Immutable List Stuff
import android.support.annotation.NonNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;
/**
* Quick and simple immutable list for easy use with auto-value. Methods that you cannot use
* are marked deprecated and will throw an unsupported operation exception.
* Delegates all methods to {@link Collections#unmodifiableList(List)}
*
* @param <E> The type this collection contains.
*/
public final class ImmutableList<E> implements List<E>, RandomAccess, Serializable {
private static final ImmutableList<?> EMPTY_LIST = new ImmutableList<>(Collections.emptyList());
/**
* Create a new ImmutableList out of the provided collection.
*
* @param collection The collection to create a ImmutableList from.
* @param <E> The type the collection contains.
* @return a new ImmutableList
* @throws NullPointerException if the provided collection is null.
*/
public static <E> ImmutableList<E> create(Collection<E> collection) {
if (collection.isEmpty()) {
return emptyList();
}
return new ImmutableList<>(collection);
}
@SuppressWarnings("unchecked")
public static <E> ImmutableList<E> emptyList() {
return (ImmutableList<E>) EMPTY_LIST;
}
public static <E> ImmutableList<E> singletonList(E item) {
return create(Collections.singletonList(item));
}
private final List<E> delegate;
private ImmutableList(Collection<E> collection) {
this.delegate = Collections.unmodifiableList(new ArrayList<>(collection));
}
@Override public int size() {
return delegate.size();
}
@Override public boolean isEmpty() {
return delegate.isEmpty();
}
@Override public boolean contains(Object o) {
return delegate.contains(o);
}
@NonNull @Override public Iterator<E> iterator() {
return delegate.iterator();
}
@NonNull @Override public Object[] toArray() {
return delegate.toArray();
}
@NonNull @Override public <T> T[] toArray(@NonNull T[] a) {
return delegate.toArray(a);
}
@Deprecated @Override public boolean add(E e) {
return delegate.add(e);
}
@Override public boolean remove(Object o) {
return delegate.remove(o);
}
@Override public boolean containsAll(@NonNull Collection<?> c) {
return delegate.containsAll(c);
}
@Deprecated @Override public boolean addAll(@NonNull Collection<? extends E> c) {
return delegate.addAll(c);
}
@Deprecated @Override public boolean addAll(int index, @NonNull Collection<? extends E> c) {
return delegate.addAll(index, c);
}
@Deprecated @Override public boolean removeAll(@NonNull Collection<?> c) {
return delegate.removeAll(c);
}
@Deprecated @Override public boolean retainAll(@NonNull Collection<?> c) {
return delegate.retainAll(c);
}
@Deprecated @Override public void clear() {
delegate.clear();
}
@Override public E get(int index) {
return delegate.get(index);
}
@Deprecated @Override public E set(int index, E element) {
return delegate.set(index, element);
}
@Deprecated @Override public void add(int index, E element) {
delegate.add(index, element);
}
@Deprecated @Override public E remove(int index) {
return delegate.remove(index);
}
@Override public int indexOf(Object o) {
return delegate.indexOf(o);
}
@Override public int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}
@Override public ListIterator<E> listIterator() {
return delegate.listIterator();
}
@NonNull @Override public ListIterator<E> listIterator(int index) {
return delegate.listIterator(index);
}
@NonNull @Override public List<E> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}
@Override public String toString() {
return delegate.toString();
}
@Override public boolean equals(Object o) {
return delegate.equals(o);
}
@Override public int hashCode() {
return delegate.hashCode();
}
}
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import com.squareup.moshi.Types;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public final class ImmutableListJsonAdapter<T> extends JsonAdapter<ImmutableList<T>> {
public static final Factory FACTORY = (type, annotations, moshi) -> {
Class<?> rawType = Types.getRawType(type);
if (!annotations.isEmpty()) return null;
if (rawType != ImmutableList.class) {
return null;
}
Type elementType = Types.collectionElementType(type, List.class);
JsonAdapter<?> elementAdapter = moshi.adapter(elementType);
return new ImmutableListJsonAdapter<>(elementAdapter);
};
private final JsonAdapter<T> elementAdapter;
private ImmutableListJsonAdapter(JsonAdapter<T> elementAdapter) {
this.elementAdapter = elementAdapter;
}
@Override public ImmutableList<T> fromJson(JsonReader reader)
throws IOException {
List<T> result = new ArrayList<>();
reader.beginArray();
while (reader.hasNext()) {
result.add(elementAdapter.fromJson(reader));
}
reader.endArray();
return ImmutableList.create(result);
}
@Override public void toJson(JsonWriter writer, ImmutableList<T> value) throws IOException {
writer.beginArray();
for (T element : value) {
elementAdapter.toJson(writer, element);
}
writer.endArray();
}
}
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import spock.lang.Specification
import spock.lang.Unroll
import java.lang.Void as Should
class ImmutableListJsonAdapterSpec extends Specification {
@Unroll Should "deserialize json list #input to ImmutableList"() {
given:
def moshi = new Moshi.Builder().with {
add(ImmutableListJsonAdapter.FACTORY)
build()
}
def jsonAdapter = moshi.adapter(type)
when:
ImmutableList result = jsonAdapter.fromJson(JsonOutput.toJson(input))
then:
noExceptionThrown()
result == input // contents should functionally be the same
where:
input | type
[] | Types.newParameterizedType(ImmutableList, String)
['test', 'one', 'two'] | Types.newParameterizedType(ImmutableList, String)
[1, null, 3] | Types.newParameterizedType(ImmutableList, Integer)
[new Test(a: 'test', b: 1)] | Types.newParameterizedType(ImmutableList, Test)
}
@Unroll Should "serialize ImmutableList #input to json array"() {
given:
def moshi = new Moshi.Builder().with {
add(ImmutableListJsonAdapter.FACTORY)
build()
}
def immutableListJsonAdapter = moshi.adapter(type)
def listJsonAdapter = moshi.adapter(Types.newParameterizedType(List, *type.actualTypeArguments))
when:
String result = immutableListJsonAdapter.toJson(ImmutableList.create(input))
String expected = listJsonAdapter.toJson(input) // create result from regular list type
then:
noExceptionThrown()
result == expected // contents should be the same
where:
input | type
[] | Types.newParameterizedType(ImmutableList, String)
['test', 'one', 'two'] | Types.newParameterizedType(ImmutableList, String)
[1, null, 3] | Types.newParameterizedType(ImmutableList, Integer)
[new Test(a: 'test', b: 1)] | Types.newParameterizedType(ImmutableList, Test)
}
Should "return empty list when encountering null"() {
given:
def moshi = new Moshi.Builder().with {
add(ImmutableListJsonAdapter.FACTORY)
build()
}
def jsonAdapter = moshi.adapter(Types.newParameterizedType(ImmutableList, Object));
when:
ImmutableList result = jsonAdapter.fromJson(JsonOutput.toJson(null))
then:
result == []
}
}
@CompileStatic
@Immutable
class Test {
String a
Integer b
}
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public final class ImmutableListTypeAdapter<E> extends TypeAdapter<ImmutableList<E>> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (rawType != ImmutableList.class) {
return null;
}
Type type = typeToken.getType();
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
TypeAdapter<T> adapter = new ImmutableListTypeAdapter(elementAdapter);
return adapter;
}
};
private final TypeAdapter<E> elementAdapter;
private ImmutableListTypeAdapter(TypeAdapter<E> elementAdapter) {
this.elementAdapter = elementAdapter;
}
@Override public void write(JsonWriter out, ImmutableList<E> value) throws IOException {
out.beginArray();
for (E element : value) {
String json = elementAdapter.toJson(element);
out.jsonValue(json);
}
out.endArray();
}
@Override public ImmutableList<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
// we don't want null in our code instead return a empty list
return ImmutableList.emptyList();
}
List<E> result = new ArrayList<>();
in.beginArray();
while(in.hasNext()) {
E instance = elementAdapter.read(in);
result.add(instance);
}
in.endArray();
return ImmutableList.create(result);
}
}
import com.google.common.reflect.TypeToken
import com.google.gson.GsonBuilder
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import spock.lang.Specification
import spock.lang.Unroll
import java.lang.Void as Should
class ImmutableListTypeAdapterSpec extends Specification {
@Unroll Should "deserialize json list #input to ImmutableList"() {
given:
def gson = GsonBuilder.newInstance().with {
registerTypeAdapterFactory(ImmutableListTypeAdapter.FACTORY)
create()
}
when:
ImmutableList result = gson.fromJson(JsonOutput.toJson(input), type)
then:
noExceptionThrown()
result == input // contents should functionally be the same
where:
input | type
[] | new TypeToken<ImmutableList<String>>() {}.type
['test', 'one', 'two'] | new TypeToken<ImmutableList<String>>() {}.type
[1, null, 3] | new TypeToken<ImmutableList<Integer>>() {}.type
[new Test(a: 'test', b: 1)] | new TypeToken<ImmutableList<Test>>(){}.type
}
@Unroll Should "serialize ImmutableList #input to json array"() {
given:
def gson = GsonBuilder.newInstance().with {
registerTypeAdapterFactory(ImmutableListTypeAdapter.FACTORY)
create()
}
when:
String result = gson.toJson(ImmutableList.create(input))
String expected = gson.toJson(input) // create result from regular list type
then:
noExceptionThrown()
result == expected // contents should be the same
where:
input | _
[] | _
['test', 'one', 'two'] | _
[1, null, 3] | _
[new Test(a: 'test', b: 1)] | _
}
Should "return empty list when encountering null"() {
given:
def gson = GsonBuilder.newInstance().with {
registerTypeAdapterFactory(ImmutableListTypeAdapter.FACTORY)
create()
}
when:
ImmutableList result = gson.fromJson(JsonOutput.toJson(null), new TypeToken<ImmutableList<Object>>() {}.type)
then:
result == []
}
@CompileStatic
@Immutable
class Test {
String a
Integer b
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment