Skip to content

Commit

Permalink
Experiments with Guava collections.
Browse files Browse the repository at this point in the history
  • Loading branch information
eamonnmcmanus committed Oct 1, 2024
1 parent 56c9c73 commit cf0c0f3
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* 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.google.gson.typeadapters;

import static java.util.Objects.requireNonNull;

import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nullable;

/**
* {@link TypeAdapterFactory} for the most common {@code com.google.common.collect} classes from
* Guava. This currently supports {@link ImmutableList}, {@link ImmutableSet}, {@link
* ImmutableSortedSet}, {@link ImmutableMap}, {@link ImmutableSortedMap}, and {@link
* ImmutableBiMap}.
*/
public class GuavaCollectionsTypeAdapterFactory implements TypeAdapterFactory {
public static final GuavaCollectionsTypeAdapterFactory INSTANCE =
new GuavaCollectionsTypeAdapterFactory();

private static final ImmutableMap<Class<?>, Function<List<?>, Collection<?>>> COLLECTION_TYPES =
ImmutableMap.of(
ImmutableList.class,
ImmutableList::copyOf,
ImmutableSet.class,
ImmutableSet::copyOf,
ImmutableSortedSet.class,
ImmutableSortedSet::copyOf);

private static final ImmutableMap<Class<?>, Function<Map<?, ?>, Map<?, ?>>> MAP_TYPES =
ImmutableMap.of(
ImmutableMap.class,
ImmutableMap::copyOf,
ImmutableSortedMap.class,
ImmutableSortedMap::copyOf,
ImmutableBiMap.class,
ImmutableBiMap::copyOf);

private GuavaCollectionsTypeAdapterFactory() {}

@Override
@Nullable
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> typeToken) {
Class<?> raw;
if (typeToken.getType() instanceof Class<?>) {
raw = (Class<?>) typeToken.getType();
} else if (typeToken.getType() instanceof ParameterizedType) {
raw = (Class<?>) ((ParameterizedType) typeToken.getType()).getRawType();
} else {
return null;
}
if (COLLECTION_TYPES.containsKey(raw)) {
Type[] typeArguments = typeToken.getType() instanceof ParameterizedType
? ((ParameterizedType) typeToken.getType()).getActualTypeArguments()
: new Type[] {Object.class};
@SuppressWarnings("unchecked")
TypeToken<List<?>> listType =
(TypeToken<List<?>>) TypeToken.getParameterized(List.class, typeArguments);
TypeAdapter<List<?>> listAdapter = requireNonNull(gson.getAdapter(listType));
return new CollectionTypeAdapter<>(listAdapter, COLLECTION_TYPES.get(raw));
} else if (MAP_TYPES.containsKey(raw)) {
Type[] typeArguments =
typeToken.getType() instanceof ParameterizedType
? ((ParameterizedType) typeToken.getType()).getActualTypeArguments()
: new Type[] {Object.class, Object.class};
@SuppressWarnings("unchecked")
TypeToken<Map<?, ?>> mapType =
(TypeToken<Map<?, ?>>) TypeToken.getParameterized(Map.class, typeArguments);
TypeAdapter<Map<?, ?>> mapAdapter = requireNonNull(gson.getAdapter(mapType));
return new MapTypeAdapter<>(mapAdapter, MAP_TYPES.get(raw));
} else {
return null;
}
}

private static class CollectionTypeAdapter<T> extends TypeAdapter<T> {
private final TypeAdapter<List<?>> listAdapter;
private final Function<List<?>, Collection<?>> copyOfListFunction;

CollectionTypeAdapter(
TypeAdapter<List<?>> listAdapter, Function<List<?>, Collection<?>> copyOfListFunction) {
this.listAdapter = listAdapter;
this.copyOfListFunction = copyOfListFunction;
}

@Override
public T read(JsonReader in) throws IOException {
@SuppressWarnings("unchecked")
T value = (T) copyOfListFunction.apply(listAdapter.read(in));
return value;
}

@Override
public void write(JsonWriter out, T value) throws IOException {
listAdapter.write(out, (List<?>) value);
}
}

private static class MapTypeAdapter<T> extends TypeAdapter<T> {
private final TypeAdapter<Map<?, ?>> mapAdapter;
private final Function<Map<?, ?>, Map<?, ?>> copyOfMapFunction;

MapTypeAdapter(
TypeAdapter<Map<?, ?>> mapAdapter, Function<Map<?, ?>, Map<?, ?>> copyOfMapFunction) {
this.mapAdapter = mapAdapter;
this.copyOfMapFunction = copyOfMapFunction;
}

@Override
public T read(JsonReader in) throws IOException {
@SuppressWarnings("unchecked")
T value = (T) copyOfMapFunction.apply(mapAdapter.read(in));
return value;
}

@Override
public void write(JsonWriter out, T value) throws IOException {
mapAdapter.write(out, (Map<?, ?>) value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.google.gson.typeadapters;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class GuavaCollectionsTypeAdapterFactoryTest {
private static final Gson GSON =
new GsonBuilder()
.registerTypeAdapterFactory(GuavaCollectionsTypeAdapterFactory.INSTANCE)
.create();

@Test
public void immutableList_object() {
ImmutableList<Object> list = ImmutableList.of("foo", 23.0, ImmutableList.of("bar", "baz"));
String json = GSON.toJson(list);
assertThat(json).isEqualTo("[\"foo\",23.0,[\"bar\",\"baz\"]]");
ImmutableList<Object> result = GSON.fromJson(json, new TypeToken<ImmutableList<Object>>() {});
assertThat(result).isEqualTo(list);
}

@Test
public void immutableList_nestedList() {
ImmutableList<ImmutableList<String>> list =
ImmutableList.of(ImmutableList.of(), ImmutableList.of("foo", "bar"));
String json = GSON.toJson(list);
assertThat(json).isEqualTo("[[],[\"foo\",\"bar\"]]");
ImmutableList<ImmutableList<String>> result =
GSON.fromJson(json, new TypeToken<ImmutableList<ImmutableList<String>>>() {});
assertThat(result).isEqualTo(list);
}

@Test
public void immutableList_raw() {
ImmutableList<String> list = ImmutableList.of("foo", "bar");
String json = GSON.toJson(list);
assertThat(json).isEqualTo("[\"foo\",\"bar\"]");
@SuppressWarnings("unchecked")
ImmutableList<String> result = GSON.fromJson(json, ImmutableList.class);
assertThat(result).isEqualTo(list);
}

@Test
public void immutableSet_object() {
ImmutableSet<Object> set = ImmutableSet.of("foo", 23.0, ImmutableList.of("bar", "baz"));
String json = GSON.toJson(set);
assertThat(json).isEqualTo("[\"foo\",23.0,[\"bar\",\"baz\"]]");
ImmutableSet<Object> result = GSON.fromJson(json, new TypeToken<ImmutableSet<Object>>() {});
assertThat(result).isEqualTo(set);
}

@Test
public void immutableSortedSet_string() {
ImmutableSortedSet<String> set = ImmutableSortedSet.of("foo", "bar", "baz");
String json = GSON.toJson(set);
assertThat(json).isEqualTo("[\"bar\",\"baz\",\"foo\"]");
ImmutableSortedSet<String> result =
GSON.fromJson(json, new TypeToken<ImmutableSortedSet<String>>() {});
assertThat(result).isEqualTo(set);
}

@Test
public void immutableMap_string() {
ImmutableMap<String, String> map = ImmutableMap.of("foo", "bar", "baz", "buh");
String json = GSON.toJson(map);
assertThat(json).isEqualTo("{\"foo\":\"bar\",\"baz\":\"buh\"}");
ImmutableMap<String, String> result =
GSON.fromJson(json, new TypeToken<ImmutableMap<String, String>>() {});
assertThat(result).isEqualTo(map);
}

@Test
public void immutableSortedMap_string() {
ImmutableSortedMap<String, String> map = ImmutableSortedMap.of("foo", "bar", "baz", "buh");
String json = GSON.toJson(map);
assertThat(json).isEqualTo("{\"baz\":\"buh\",\"foo\":\"bar\"}");
ImmutableSortedMap<String, String> result =
GSON.fromJson(json, new TypeToken<ImmutableSortedMap<String, String>>() {});
assertThat(result).isEqualTo(map);
}

@Test
public void immutableBiMap_string() {
ImmutableBiMap<String, String> map = ImmutableBiMap.of("foo", "bar", "baz", "buh");
String json = GSON.toJson(map);
assertThat(json).isEqualTo("{\"foo\":\"bar\",\"baz\":\"buh\"}");
ImmutableBiMap<String, String> result =
GSON.fromJson(json, new TypeToken<ImmutableBiMap<String, String>>() {});
assertThat(result).isEqualTo(map);
}
}

0 comments on commit cf0c0f3

Please sign in to comment.