Last active
December 25, 2018 22:56
-
-
Save AndrewReitz/48fbff8ef47be3f79424a6cf6bb8f0aa to your computer and use it in GitHub Desktop.
Immutable List Stuff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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