141 lines
4.7 KiB
Java
141 lines
4.7 KiB
Java
package tc.oc.commons.core.util;
|
|
|
|
import java.util.Collection;
|
|
import java.util.Map;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Set;
|
|
import java.util.function.Supplier;
|
|
|
|
import com.google.common.collect.ForwardingSetMultimap;
|
|
import com.google.common.collect.HashMultimap;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.common.collect.Multimaps;
|
|
import com.google.common.collect.SetMultimap;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.common.reflect.TypeToken;
|
|
|
|
/**
|
|
* A {@link SetMultimap} with type keys that supports lookups based on type bounds.
|
|
*
|
|
* Keys are {@link TypeToken}s for types that extend {@link K} i.e. {@code TypeToken<? extends K>}.
|
|
* Wherever {@link Class}es are accepted as keys, they are converted to the equivalent {@link TypeToken}.
|
|
*
|
|
* Note that unlike most map collections, the key type parameter is not the type of the keys,
|
|
* but rather a bound for the keys, which are types themselves.
|
|
*
|
|
* There is no required relationship between the types of keys and values.
|
|
*/
|
|
public class TypeMap<K, V> extends ForwardingSetMultimap<TypeToken<? extends K>, V> implements MultimapHelper<TypeToken<? extends K>, V> {
|
|
|
|
public static <K, V> TypeMap<K, V> create() {
|
|
return new TypeMap<>(HashMultimap.create());
|
|
}
|
|
|
|
public static <K, V> TypeMap<K, V> wrap(SetMultimap<TypeToken<? extends K>, V> map) {
|
|
return new TypeMap<>(map);
|
|
}
|
|
|
|
private final SetMultimap<TypeToken<? extends K>, V> map;
|
|
|
|
public TypeMap(SetMultimap<TypeToken<? extends K>, V> map) {
|
|
this.map = map;
|
|
}
|
|
|
|
public TypeMap(Map<TypeToken<? extends K>, ? extends Collection<V>> map, Supplier<Set<V>> supplier) {
|
|
this(Multimaps.newSetMultimap((Map) map, supplier::get));
|
|
}
|
|
|
|
@Override
|
|
protected SetMultimap<TypeToken<? extends K>, V> delegate() {
|
|
return map;
|
|
}
|
|
|
|
public boolean put(Class<? extends K> key, V value) {
|
|
return super.put(TypeToken.of(key), value);
|
|
}
|
|
|
|
/**
|
|
* Return all keys within the given bounds
|
|
*/
|
|
public Set<TypeToken<? extends K>> keysAssignableTo(TypeToken<? extends K> bounds) {
|
|
return Sets.filter(keySet(), bounds::isAssignableFrom);
|
|
}
|
|
|
|
/**
|
|
* Return all keys that bound the given type
|
|
*/
|
|
public Set<TypeToken<? extends K>> keysAssignableFrom(TypeToken<? extends K> type) {
|
|
return Sets.filter(keySet(), bounds -> bounds.isAssignableFrom(type));
|
|
}
|
|
|
|
public Set<TypeToken<? extends K>> keysAssignableTo(Class<? extends K> bounds) {
|
|
return keysAssignableTo(TypeToken.of(bounds));
|
|
}
|
|
|
|
public Set<TypeToken<? extends K>> keysAssignableFrom(Class<? extends K> type) {
|
|
return keysAssignableFrom(TypeToken.of(type));
|
|
}
|
|
|
|
/**
|
|
* Return all values assigned to keys within the given bounds
|
|
*/
|
|
public Set<V> allAssignableTo(TypeToken<? extends K> bounds) {
|
|
return new SupersetView(Iterables.transform(keysAssignableTo(bounds), this::get));
|
|
}
|
|
|
|
/**
|
|
* Return all values assigned to keys that bound the given type
|
|
*/
|
|
public Set<V> allAssignableFrom(TypeToken<? extends K> type) {
|
|
return new SupersetView(Iterables.transform(keysAssignableFrom(type), this::get));
|
|
}
|
|
|
|
/**
|
|
* Return all values assigned to keys within the given bounds
|
|
*/
|
|
public Set<V> allAssignableTo(Class<? extends K> bounds) {
|
|
return allAssignableTo(TypeToken.of(bounds));
|
|
}
|
|
|
|
/**
|
|
* Return all values assigned to keys that bound the given type
|
|
*/
|
|
public Set<V> allAssignableFrom(Class<? extends K> type) {
|
|
return allAssignableFrom(TypeToken.of(type));
|
|
}
|
|
|
|
/**
|
|
* Return a single value assigned to a key within the given bounds
|
|
* @throws NoSuchElementException if no such value exists
|
|
* @throws AmbiguousElementException if multiple such values exist
|
|
*/
|
|
public V oneAssignableTo(TypeToken<? extends K> bounds) {
|
|
try {
|
|
return Iterables.getOnlyElement(allAssignableTo(bounds));
|
|
} catch(IllegalArgumentException e) {
|
|
throw new AmbiguousElementException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a single value assigned to a key that bounds the given type
|
|
* @throws NoSuchElementException if no such value exists
|
|
* @throws AmbiguousElementException if multiple such values exist
|
|
*/
|
|
public V oneAssignableFrom(TypeToken<? extends K> type) {
|
|
try {
|
|
return Iterables.getOnlyElement(allAssignableFrom(type));
|
|
} catch(IllegalArgumentException e) {
|
|
throw new AmbiguousElementException();
|
|
}
|
|
}
|
|
|
|
public V oneAssignableTo(Class<? extends K> bounds) {
|
|
return oneAssignableTo(TypeToken.of(bounds));
|
|
}
|
|
|
|
public V oneAssignableFrom(Class<? extends K> bounds) {
|
|
return oneAssignableFrom(TypeToken.of(bounds));
|
|
}
|
|
}
|