/*
* 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 org.jdbi.v3.guava;
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.TreeMultimap;
import org.jdbi.v3.core.collector.CollectorFactory;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Comparator;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collector;
import static org.jdbi.v3.core.collector.BuiltInCollectorFactory.toMap;
import static org.jdbi.v3.core.generic.GenericTypes.findGenericParameter;
import static org.jdbi.v3.core.generic.GenericTypes.getErasedType;
import static org.jdbi.v3.core.generic.GenericTypes.resolveMapEntryType;
import static org.jdbi.v3.core.generic.GenericTypes.resolveType;
/**
* Provides Collectors for Guava collection types.
* <p>Supported container types:</p>
* <ul>
* <li>com.google.common.base.Optional<T> (throws an exception if more than one row in result)</li>
* <li>com.google.common.collect.ImmutableList<T></li>
* <li>com.google.common.collect.ImmutableSet<T></li>
* <li>com.google.common.collect.ImmutableSortedSet<T extends Comparable></li>
* </ul>
* <p>Supported Maps and Multimaps types - for rows mapped to Map.Entry<K, V>:</p>
* <ul>
* <li>com.google.common.collect.BiMap<K, V></li>
* <li>com.google.common.collect.ImmutableMap<K, V></li>
* <li>com.google.common.collect.Multimap<K, V></li>
* <li>com.google.common.collect.ListMultimap<K, V></li>
* <li>com.google.common.collect.ArrayListMultimap<K, V></li>
* <li>com.google.common.collect.LinkedListMultimap<K, V></li>
* <li>com.google.common.collect.SetMultimap<K, V></li>
* <li>com.google.common.collect.HashMultimap<K, V></li>
* <li>com.google.common.collect.TreeMultimap<K, V></li>
* <li>com.google.common.collect.ImmutableMultimap<K, V></li>
* <li>com.google.common.collect.ImmutableListMultimap<K, V></li>
* <li>com.google.common.collect.ImmutableSetMultimap<K, V></li>
* </ul>
*/
public class GuavaCollectors {
/**
* @return a {@code CollectorFactory} which knows how to create all supported Guava types
*/
public static CollectorFactory factory() {
return new Factory();
}
public static class Factory implements CollectorFactory {
private final TypeVariable<Class<Multimap>> multimapKey;
private final TypeVariable<Class<Multimap>> multimapValue;
public Factory() {
TypeVariable<Class<Multimap>>[] multimapParams = Multimap.class.getTypeParameters();
multimapKey = multimapParams[0];
multimapValue = multimapParams[1];
}
private final Map<Class<?>, Collector<?, ?, ?>> collectors =
ImmutableMap.<Class<?>, Collector<?, ?, ?>>builder()
.put(ImmutableList.class, ImmutableList.toImmutableList())
.put(ImmutableSet.class, ImmutableSet.toImmutableSet())
.put(ImmutableSortedSet.class, ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder()))
.put(Optional.class, toOptional())
.put(ImmutableMap.class, toImmutableMap())
.put(BiMap.class, toHashBiMap())
.put(ImmutableMultimap.class, toImmutableListMultimap())
.put(ImmutableListMultimap.class, toImmutableListMultimap())
.put(ImmutableSetMultimap.class, toImmutableSetMultimap())
.put(Multimap.class, toImmutableListMultimap())
.put(ListMultimap.class, toImmutableListMultimap())
.put(ArrayListMultimap.class, toArrayListMultimap())
.put(LinkedListMultimap.class, toLinkedListMultimap())
.put(SetMultimap.class, toImmutableSetMultimap())
.put(HashMultimap.class, toHashMultimap())
.put(TreeMultimap.class, toTreeMultimap())
.build();
@Override
public boolean accepts(Type containerType) {
Class<?> erasedType = getErasedType(containerType);
return collectors.containsKey(erasedType) && containerType instanceof ParameterizedType;
}
@Override
public java.util.Optional<Type> elementType(Type containerType) {
Class<?> erasedType = getErasedType(containerType);
if (Multimap.class.isAssignableFrom(erasedType)) {
Type keyType = resolveType(multimapKey, containerType);
Type valueType = resolveType(multimapValue, containerType);
return java.util.Optional.of(resolveMapEntryType(keyType, valueType));
} else if (Map.class.isAssignableFrom(erasedType)) {
return java.util.Optional.of(resolveMapEntryType(containerType));
}
return findGenericParameter(containerType, erasedType);
}
@Override
public Collector<?, ?, ?> build(Type containerType) {
return collectors.get(getErasedType(containerType));
}
}
/**
* Returns a {@code Collector} that accumulates 0 or 1 input elements into Guava's {@code Optional<T>}.
* The returned collector will throw {@code IllegalStateException} whenever 2 or more elements
* are present in a stream.
*
* @param <T> the collected type
* @return a {@code Collector} which collects 0 or 1 input elements into a Guava {@code Optional<T>}.
*/
public static <T> Collector<T, ?, Optional<T>> toOptional() {
return Collector.<T, GuavaOptionalBuilder<T>, Optional<T>>of(
GuavaOptionalBuilder::new,
GuavaOptionalBuilder::set,
(left, right) -> left.build().isPresent() ? left : right,
GuavaOptionalBuilder::build);
}
private static class GuavaOptionalBuilder<T> {
private Optional<T> optional = Optional.absent();
public void set(T value) {
if (optional.isPresent()) {
throw new IllegalStateException(
String.format("Multiple values for Optional type: ['%s','%s',...]",
optional.get(),
value));
}
optional = Optional.of(value);
}
public Optional<T> build() {
return optional;
}
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into an
* {@code ImmutableMap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into an {@code ImmutableMap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, ImmutableMap<K, V>> toImmutableMap() {
return ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into a
* {@code HashBiMap<K, V>}. The returned collector will throw {@code IllegalStateException}
* whenever a set of input elements contains multiple entries with the same key.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into a {@code HashBiMap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, BiMap<K, V>> toHashBiMap() {
return toMap(HashBiMap::create);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into an
* {@code ImmutableListMultimap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into an {@code ImmutableListMultimap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap() {
return Collector.of(
ImmutableListMultimap::<K, V>builder,
ImmutableListMultimap.Builder::put,
GuavaCollectors::combine,
ImmutableListMultimap.Builder::build);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into an
* {@code ImmutableSetMultimap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into an {@code ImmutableSetMultimap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap() {
return Collector.of(
ImmutableSetMultimap::<K, V>builder,
ImmutableSetMultimap.Builder::put,
GuavaCollectors::combine,
ImmutableSetMultimap.Builder::build);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into an
* {@code ArrayListMultimap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into an {@code ArrayListMultimap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, ArrayListMultimap<K, V>> toArrayListMultimap() {
return toMultimap(ArrayListMultimap::create);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into a
* {@code LinkedListMultimap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into a {@code LinkedListMultimap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, LinkedListMultimap<K, V>> toLinkedListMultimap() {
return toMultimap(LinkedListMultimap::create);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into a
* {@code HashMultimap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into a {@code ArrayListMultimap},
* in encounter order.
*/
public static <K, V> Collector<Map.Entry<K, V>, ?, HashMultimap<K, V>> toHashMultimap() {
return toMultimap(HashMultimap::create);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into a
* {@code TreeMultimap<K, V>}.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @return a {@code Collector} which collects map entry elements into a {@code TreeMultimap},
* in encounter order.
*/
public static <K extends Comparable, V extends Comparable> Collector<Map.Entry<K, V>, ?, TreeMultimap<K, V>> toTreeMultimap() {
return toMultimap(TreeMultimap::create);
}
/**
* Returns a {@code Collector} that accumulates {@code Map.Entry<K, V>} input elements into a
* {@code Multimap<K, V>} of the supplied type.
*
* @param <K> the type of map keys
* @param <V> the type of map values
* @param multimapFactory a {@code Supplier} which return a new, empty {@code Multimap} of the appropriate type.
* @return a {@code Collector} which collects map entry elements into a {@code Multiamp}, in encounter order.
*/
public static <K, V, M extends Multimap<K, V>> Collector<Map.Entry<K, V>, ?, M> toMultimap(Supplier<M> multimapFactory) {
return Collector.of(
multimapFactory,
GuavaCollectors::putEntry,
GuavaCollectors::combine);
}
private static <K, V, M extends Multimap<K, V>> void putEntry(M map, Map.Entry<K, V> entry) {
map.put(entry.getKey(), entry.getValue());
}
private static <K, V, M extends Multimap<K, V>> M combine(M a, M b) {
a.putAll(b);
return a;
}
private static <K, V, MB extends ImmutableMultimap.Builder<K, V>> MB combine(MB a, MB b) {
a.putAll(b.build());
return a;
}
}