blob: 2657a52961608bc040cfaf444c2c34a505d2990c [file] [log] [blame]
/*
**********************************************************************
* Copyright (c) 2002-2012, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Mark Davis
**********************************************************************
*/
package com.ibm.icu.dev.util;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.ibm.icu.util.Freezable;
/**
* A Relation is a set of mappings from keys to values.
* Unlike Map, there is not guaranteed to be a single value per key.
* The Map-like APIs return collections for values.
* @author medavis
*/
public class Relation<K, V> implements Freezable { // TODO: add , Map<K, Collection<V>>, but requires API changes
private Map<K, Set<V>> data;
Constructor<Set<V>> setCreator;
Object[] setComparatorParam;
public static <K,V> Relation<K, V> of(Map<K, Set<V>> map, Class<?> setCreator) {
return new Relation(map, setCreator);
}
public static <K,V> Relation<K, V> of(Map<K, Set<V>> map, Class setCreator, Comparator<V> setComparator) {
return new Relation(map, setCreator, setComparator);
}
public Relation(Map<K, Set<V>> map, Class<Set<V>> setCreator) {
this(map, setCreator, null);
}
public Relation(Map<K, Set<V>> map, Class<Set<V>> setCreator, Comparator<V> setComparator) {
try {
setComparatorParam = setComparator == null ? null : new Object[]{setComparator};
if (setComparator == null) {
this.setCreator = setCreator.getConstructor();
this.setCreator.newInstance(setComparatorParam); // check to make sure compiles
} else {
this.setCreator = setCreator.getConstructor(Comparator.class);
this.setCreator.newInstance(setComparatorParam); // check to make sure compiles
}
data = map == null ? new HashMap() : map;
} catch (Exception e) {
throw (RuntimeException) new IllegalArgumentException("Can't create new set").initCause(e);
}
}
public void clear() {
data.clear();
}
public boolean containsKey(Object key) {
return data.containsKey(key);
}
public boolean containsValue(Object value) {
for (Set<V> values : data.values()) {
if (values.contains(value)) {
return true;
}
}
return false;
}
public final Set<Entry<K, V>> entrySet() {
return keyValueSet();
}
public Set<Entry<K, Set<V>>> keyValuesSet() {
return data.entrySet();
}
public Set<Entry<K, V>> keyValueSet() {
Set<Entry<K, V>> result = new LinkedHashSet();
for (K key : data.keySet()) {
for (V value : data.get(key)) {
result.add(new SimpleEntry(key, value));
}
}
return result;
}
public boolean equals(Object o) {
if (o == null)
return false;
if (o.getClass() != this.getClass())
return false;
return data.equals(((Relation) o).data);
}
// public V get(Object key) {
// Set<V> set = data.get(key);
// if (set == null || set.size() == 0)
// return null;
// return set.iterator().next();
// }
public Set<V> getAll(Object key) {
return data.get(key);
}
public Set<V> get(Object key) {
return data.get(key);
}
public int hashCode() {
return data.hashCode();
}
public boolean isEmpty() {
return data.isEmpty();
}
public Set<K> keySet() {
return data.keySet();
}
public V put(K key, V value) {
Set<V> set = data.get(key);
if (set == null) {
data.put(key, set = newSet());
}
set.add(value);
return value;
}
public V putAll(K key, Collection<? extends V> values) {
Set<V> set = data.get(key);
if (set == null) {
data.put(key, set = newSet());
}
set.addAll(values);
return values.size() == 0 ? null : values.iterator().next();
}
public V putAll(Collection<K> keys, V value) {
V result = null;
for (K key : keys) {
result = put(key, value);
}
return result;
}
private Set<V> newSet() {
try {
return (Set<V>) setCreator.newInstance(setComparatorParam);
} catch (Exception e) {
throw (RuntimeException) new IllegalArgumentException("Can't create new set").initCause(e);
}
}
public void putAll(Map<? extends K, ? extends V> t) {
for (K key : t.keySet()) {
put(key, t.get(key));
}
}
public void putAll(Relation<? extends K, ? extends V> t) {
for (K key : t.keySet()) {
for (V value : t.getAll(key)) {
put(key, value);
}
}
}
public Set<V> removeAll(K key) {
try {
return data.remove(key);
} catch (NullPointerException e) {
return null; // data doesn't allow null, eg ConcurrentHashMap
}
}
public boolean remove(K key, V value) {
try {
Set<V> set = data.get(key);
if (set == null) {
return false;
}
boolean result = set.remove(value);
if (set.size() == 0) {
data.remove(key);
}
return result;
} catch (NullPointerException e) {
return false; // data doesn't allow null, eg ConcurrentHashMap
}
}
public int size() {
return data.size();
}
public Set<V> values() {
return values(new LinkedHashSet());
}
public <C extends Collection<V>> C values(C result) {
for (Entry<K, Set<V>> keyValue : data.entrySet()) {
result.addAll(keyValue.getValue());
}
return result;
}
public String toString() {
return data.toString();
}
static class SimpleEntry<K, V> implements Entry<K, V> {
K key;
V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<K, V> e) {
this.key = e.getKey();
this.value = e.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
}
public Relation<K,V> addAllInverted(Relation<V,K> source) {
for (V value : source.data.keySet()) {
for (K key : source.data.get(value)) {
put(key, value);
}
}
return this;
}
public Relation<K,V> addAllInverted(Map<V,K> source) {
for (V value : source.keySet()) {
put(source.get(value), value);
}
return this;
}
boolean frozen = false;
public boolean isFrozen() {
return frozen;
}
public Object freeze() {
if (!frozen) {
frozen = true;
// does not handle one level down, so we do that on a case-by-case basis
for (K key : data.keySet()) {
data.put(key, Collections.unmodifiableSet(data.get(key)));
}
// now do top level
data = Collections.unmodifiableMap(data);
}
return this;
}
public Object cloneAsThawed() {
// TODO do later
throw new UnsupportedOperationException();
}
public boolean removeAll(Relation<K, V> toBeRemoved) {
boolean result = false;
for (K key : toBeRemoved.keySet()) {
try {
Set<V> values = toBeRemoved.getAll(key);
if (values != null) {
result |= removeAll(key, values);
}
} catch (NullPointerException e) {
// data doesn't allow null, eg ConcurrentHashMap
}
}
return result;
}
public Set<V> removeAll(K... keys) {
return removeAll(Arrays.asList(keys));
}
public boolean removeAll(K key, Iterable<V> toBeRemoved) {
boolean result = false;
for (V value : toBeRemoved) {
result |= remove(key, value);
}
return result;
}
public Set<V> removeAll(Collection<K> toBeRemoved) {
Set<V> result = new LinkedHashSet();
for (K key : toBeRemoved) {
try {
final Set<V> removals = data.remove(key);
if (removals != null) {
result.addAll(removals);
}
} catch (NullPointerException e) {
// data doesn't allow null, eg ConcurrentHashMap
}
}
return result;
}
}