Skip to content

Instantly share code, notes, and snippets.

@naturalwarren
Last active January 31, 2019 08: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 naturalwarren/dcbf4005d336f36b2342f571273615bf to your computer and use it in GitHub Desktop.
Save naturalwarren/dcbf4005d336f36b2342f571273615bf to your computer and use it in GitHub Desktop.
Tracked Mutable Collection Classes & TypeAdapterFactories
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.model.core.adapter;
import com.google.gson.Gson;
import com.google.gson.InstanceCreator;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.ObjectConstructor;
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 com.ubercab.android.util.ArraySet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Adapt a homogeneous collection of objects. Handles collection types using TrackedMutable______ variants to track
* attempts at mutating.
*/
public final class TrackedCollectionTypeAdapterFactory implements TypeAdapterFactory {
public static final TrackedCollectionTypeAdapterFactory FACTORY = new TrackedCollectionTypeAdapterFactory();
private TrackedCollectionTypeAdapterFactory() { }
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
//noinspection unchecked create() doesn't define a type parameter
return (TypeAdapter<T>) new Adapter(elementTypeAdapter, typeToken);
}
/**
* Here's a javadoc comment because checkstyle is overbearing.
*
* @param <E> the type
*/
private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private final Class<? super Collection<E>> rawType;
private final ObjectConstructor<Collection<E>> constructor;
public Adapter(
TypeAdapter<E> elementTypeAdapter, TypeToken<Collection<E>> typeToken) {
this.elementTypeAdapter = elementTypeAdapter;
rawType = typeToken.getRawType();
constructor = new ConstructorConstructor(Collections.<Type, InstanceCreator<?>>emptyMap()).get(typeToken);
}
@Override
public Collection<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Collection<E> collection;
boolean isSet = false;
boolean isList = false;
if (rawType.isAssignableFrom(Set.class)) {
isSet = true;
collection = new ArraySet<>();
} else if (rawType.isAssignableFrom(List.class)) {
isList = true;
collection = new ArrayList<>();
} else {
collection = constructor.construct();
}
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
in.endArray();
if (isSet) {
collection = TrackedMutableSet.wrap((ArraySet<E>) collection);
} else if (isList) {
collection = TrackedMutableList.wrap((ArrayList<E>) collection);
}
return collection;
}
@Override
public void write(JsonWriter out, Collection<E> collection) throws IOException {
if (collection == null) {
out.nullValue();
return;
}
out.beginArray();
for (E element : collection) {
elementTypeAdapter.write(out, element);
}
out.endArray();
}
}
}
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.model.core.adapter;
import android.support.v4.util.ArrayMap;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.JsonReaderInternalAccess;
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.Map;
/**
* Adapts maps to either JSON objects or JSON arrays. Handles collection types using {@link TrackedMutableMap} variants
* to track attempts at mutating. Note that this only supports String keys, because JSON only supports string keys.
*/
public final class TrackedMapTypeAdapterFactory implements TypeAdapterFactory {
public static final TrackedMapTypeAdapterFactory FACTORY = new TrackedMapTypeAdapterFactory();
private TrackedMapTypeAdapterFactory() { }
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!Map.class.isAssignableFrom(rawType)) {
return null;
}
Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
if (!TypeToken.get(keyAndValueTypes[0]).getRawType().isAssignableFrom(String.class)) {
// We only care about string keys
return null;
}
TypeAdapter<?> keyAdapter = gson.getAdapter(String.class);
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
//noinspection unchecked we don't define a type parameter for the key or value types
return new Adapter(keyAdapter, valueAdapter);
}
/**
* Here's a javadoc comment because checkstyle is overbearing.
*
* @param <V> the value type
*/
private final class Adapter<V> extends TypeAdapter<Map<String, V>> {
private final TypeAdapter<String> keyTypeAdapter;
private final TypeAdapter<V> valueTypeAdapter;
Adapter(TypeAdapter<String> keyTypeAdapter, TypeAdapter<V> valueTypeAdapter) {
this.keyTypeAdapter = keyTypeAdapter;
this.valueTypeAdapter = valueTypeAdapter;
}
@Override
public Map<String, V> read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
ArrayMap<String, V> map = new ArrayMap<>();
if (peek == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
// entry array
in.beginArray();
String key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
in.endArray();
}
in.endArray();
} else {
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
String key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
in.endObject();
}
return TrackedMutableMap.wrap(map);
}
@Override
public void write(JsonWriter out, Map<String, V> map) throws IOException {
if (map == null) {
out.nullValue();
return;
}
out.beginObject();
for (Map.Entry<String, V> entry : map.entrySet()) {
out.name(String.valueOf(entry.getKey()));
valueTypeAdapter.write(out, entry.getValue());
}
out.endObject();
}
}
}
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.model.core.adapter;
import android.support.annotation.NonNull;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;
import timber.log.Timber;
/**
* Tracked wrapper list implementation that logs when mutative operations are done to its delegate.
*
* @param <E> the element type.
*/
public final class TrackedMutableList<E> implements List<E>, RandomAccess {
/**
* Static creator method to turn a list into a tracked wrapper list.
* This method should not be used and is intended for serialization purposes only.
*
* @param list fully constructed list object.
* @param <T> the type.
* @return a tracked list.
*/
public static <T> TrackedMutableList<T> wrap(@NonNull List<T> list) {
return new TrackedMutableList<>(list);
}
private List<E> delegate;
private TrackedMutableList(@NonNull List<E> delegate) {
this.delegate = delegate;
}
@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) {
//noinspection SuspiciousToArrayCall
return delegate.toArray(a);
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean add(E e) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.add(e);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean remove(Object o) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.remove(o);
}
@Override
public boolean containsAll(@NonNull Collection<?> c) {
return delegate.containsAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean addAll(@NonNull Collection<? extends E> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.addAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean addAll(int index, @NonNull Collection<? extends E> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.addAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean removeAll(@NonNull Collection<?> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.removeAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean retainAll(@NonNull Collection<?> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.retainAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public void clear() {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
delegate.clear();
}
@Override
public E get(int index) {
return delegate.get(index);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public E set(int index, E element) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
return delegate.set(index, element);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public void add(int index, E element) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
delegate.add(index, element);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public E remove(int index) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model list!");
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 TrackedMutableList.wrap(delegate.subList(fromIndex, toIndex));
}
}
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.model.core.adapter;
import android.support.annotation.NonNull;
import android.support.v4.util.ArrayMap;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import timber.log.Timber;
/**
* Tracked wrapper map implementation that logs when mutative operations are done to its delegate.
*
* @param <K> the key element type.
* @param <V> the value element type.
*/
public final class TrackedMutableMap<K, V> implements Map<K, V> {
/**
* Static creator method to turn a map into a Tracked wrapper map.
* This method should not be used and is intended for serialization purposes only.
*
* @param map fully constructed map object.
* @param <K> the type of the key.
* @param <V> the type of the value.
* @return a Tracked map.
*/
public static <K, V> TrackedMutableMap<K, V> wrap(@NonNull ArrayMap<K, V> map) {
return new TrackedMutableMap<>(map);
}
private Map<K, V> delegate;
private TrackedMutableMap(@NonNull Map<K, V> delegate) {
this.delegate = delegate;
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public V get(Object key) {
return delegate.get(key);
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public V put(K key, V value) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model map!");
return delegate.put(key, value);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public V remove(Object key) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model map!");
return delegate.remove(key);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public void putAll(@NonNull Map<? extends K, ? extends V> m) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model map!");
delegate.putAll(m);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public void clear() {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model map!");
delegate.clear();
}
@NonNull
@Override
public Set<K> keySet() {
return delegate.keySet();
}
@NonNull
@Override
public Collection<V> values() {
return delegate.values();
}
@NonNull
@Override
public Set<Entry<K, V>> entrySet() {
return delegate.entrySet();
}
}
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.model.core.adapter;
import android.support.annotation.NonNull;
import com.ubercab.android.util.ArraySet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import timber.log.Timber;
/**
* Tracked wrapper set implementation that logs when mutative operations are done to its delegate.
*
* @param <E> the element type.
*/
public final class TrackedMutableSet<E> implements Collection<E>, Set<E> {
/**
* Static creator method to turn a set into a Tracked wrapper set.
* This method should not be used and is intended for serialization purposes only.
*
* @param set fully constructed set object.
* @param <E> the type of element in the set.
* @return a Tracked set.
*/
public static <E> TrackedMutableSet<E> wrap(@NonNull ArraySet<E> set) {
return new TrackedMutableSet<>(set);
}
private Set<E> delegate;
private TrackedMutableSet(@NonNull Set<E> delegate) {
this.delegate = delegate;
}
@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) {
//noinspection SuspiciousToArrayCall
return delegate.toArray(a);
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean add(E e) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model set!");
return delegate.add(e);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean remove(Object o) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model set!");
return delegate.remove(o);
}
@Override
public boolean containsAll(@NonNull Collection<?> c) {
return delegate.containsAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean addAll(@NonNull Collection<? extends E> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model set!");
return delegate.addAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean removeAll(@NonNull Collection<?> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model set!");
return delegate.removeAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public boolean retainAll(@NonNull Collection<?> c) {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model set!");
return delegate.retainAll(c);
}
/**
* Will log an exception to notify the user tried to modify a generated model collection, which we plan to
* eventually make immutable.
*
* @deprecated Will eventually be an unsupported operation.
*/
@Deprecated
@Override
public void clear() {
Timber.e(new UnsupportedOperationException(), "Modifying a generated model set!");
delegate.clear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment