1/*
2 * Copyright (C) 2012 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.caliper.json;
18
19import com.google.common.collect.ImmutableListMultimap;
20import com.google.common.collect.ImmutableMultimap;
21import com.google.common.collect.ImmutableSetMultimap;
22import com.google.common.collect.ListMultimap;
23import com.google.common.collect.SetMultimap;
24import com.google.common.reflect.TypeParameter;
25import com.google.common.reflect.TypeToken;
26import com.google.gson.Gson;
27import com.google.gson.TypeAdapter;
28import com.google.gson.TypeAdapterFactory;
29import com.google.gson.stream.JsonReader;
30import com.google.gson.stream.JsonWriter;
31
32import java.io.IOException;
33import java.lang.reflect.ParameterizedType;
34import java.util.List;
35import java.util.Map;
36import java.util.Map.Entry;
37import java.util.Set;
38
39/**
40 * Serializes and deserializes {@link ImmutableMultimap} instances using maps of collections as
41 * intermediaries.
42 */
43final class ImmutableMultimapTypeAdapterFactory implements TypeAdapterFactory {
44  private static <K, V> TypeToken<Map<K, List<V>>> getMapOfListsToken(
45      TypeToken<ListMultimap<K, V>> from) {
46    ParameterizedType rawType = (ParameterizedType) from.getSupertype(ListMultimap.class).getType();
47    @SuppressWarnings("unchecked") // key type is K
48    TypeToken<K> keyType = (TypeToken<K>) TypeToken.of(rawType.getActualTypeArguments()[0]);
49    @SuppressWarnings("unchecked") // value type is V
50    TypeToken<V> valueType = (TypeToken<V>) TypeToken.of(rawType.getActualTypeArguments()[1]);
51    return new TypeToken<Map<K, List<V>>>() {}
52        .where(new TypeParameter<K>() {}, keyType)
53        .where(new TypeParameter<V>() {}, valueType);
54  }
55
56  private static <K, V> TypeToken<Map<K, Set<V>>> getMapOfSetsToken(
57      TypeToken<SetMultimap<K, V>> from) {
58    ParameterizedType rawType = (ParameterizedType) from.getSupertype(SetMultimap.class).getType();
59    @SuppressWarnings("unchecked") // key type is K
60    TypeToken<K> keyType = (TypeToken<K>) TypeToken.of(rawType.getActualTypeArguments()[0]);
61    @SuppressWarnings("unchecked") // value type is V
62    TypeToken<V> valueType = (TypeToken<V>) TypeToken.of(rawType.getActualTypeArguments()[1]);
63    return new TypeToken<Map<K, Set<V>>>() {}
64        .where(new TypeParameter<K>() {}, keyType)
65        .where(new TypeParameter<V>() {}, valueType);
66  }
67
68  @Override
69  @SuppressWarnings({"unchecked", "rawtypes"})
70  public <T> TypeAdapter<T> create(Gson gson, com.google.gson.reflect.TypeToken<T> typeToken) {
71    if (ImmutableListMultimap.class.isAssignableFrom(typeToken.getRawType())) {
72      TypeToken<Map<?, List<?>>> mapToken =
73          getMapOfListsToken((TypeToken) TypeToken.of(typeToken.getType()));
74      final TypeAdapter<Map<?, List<?>>> adapter =
75          (TypeAdapter<Map<?, List<?>>>) gson.getAdapter(
76              com.google.gson.reflect.TypeToken.get(mapToken.getType()));
77      return new TypeAdapter<T>() {
78        @Override public void write(JsonWriter out, T value) throws IOException {
79          ImmutableListMultimap<?, ?> multimap = (ImmutableListMultimap<?, ?>) value;
80          adapter.write(out, (Map) multimap.asMap());
81        }
82
83        @Override public T read(JsonReader in) throws IOException {
84          Map<?, List<?>> value = adapter.read(in);
85          ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
86          for (Entry<?, List<?>> entry : value.entrySet()) {
87            builder.putAll(entry.getKey(), entry.getValue());
88          }
89          return (T) builder.build();
90        }
91      };
92    } else if (ImmutableSetMultimap.class.isAssignableFrom(typeToken.getRawType())) {
93      TypeToken<Map<?, Set<?>>> mapToken =
94          getMapOfSetsToken((TypeToken) TypeToken.of(typeToken.getType()));
95      final TypeAdapter<Map<?, Set<?>>> adapter =
96          (TypeAdapter<Map<?, Set<?>>>) gson.getAdapter(
97              com.google.gson.reflect.TypeToken.get(mapToken.getType()));
98      return new TypeAdapter<T>() {
99        @Override public void write(JsonWriter out, T value) throws IOException {
100          ImmutableSetMultimap<?, ?> multimap = (ImmutableSetMultimap<?, ?>) value;
101          adapter.write(out, (Map) multimap.asMap());
102        }
103
104        @Override public T read(JsonReader in) throws IOException {
105          Map<?, Set<?>> value = adapter.read(in);
106          ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
107          for (Entry<?, Set<?>> entry : value.entrySet()) {
108            builder.putAll(entry.getKey(), entry.getValue());
109          }
110          return (T) builder.build();
111        }
112      };
113    } else {
114      return null;
115    }
116  }
117}
118