com.ibm.icu.impl.Relation Maven / Gradle / Ivy
The newest version!
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
**********************************************************************
* Copyright (c) 2002-2015, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Mark Davis
**********************************************************************
*/
package com.ibm.icu.impl;
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 implements Freezable> { // TODO: add , Map>, but requires API changes
private Map> data;
Constructor extends Set> setCreator;
Object[] setComparatorParam;
public static Relation of(Map> map, Class> setCreator) {
return new Relation<>(map, setCreator);
}
public static Relation of(Map> map, Class> setCreator, Comparator setComparator) {
return new Relation<>(map, setCreator, setComparator);
}
public Relation(Map> map, Class> setCreator) {
this(map, setCreator, null);
}
@SuppressWarnings("unchecked")
public Relation(Map> map, Class> setCreator, Comparator setComparator) {
try {
setComparatorParam = setComparator == null ? null : new Object[]{setComparator};
if (setComparator == null) {
this.setCreator = ((Class extends Set>)setCreator).getConstructor();
this.setCreator.newInstance(setComparatorParam); // check to make sure compiles
} else {
this.setCreator = ((Class extends Set>)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 values : data.values()) {
if (values.contains(value)) {
return true;
}
}
return false;
}
public final Set> entrySet() {
return keyValueSet();
}
public Set>> keyValuesSet() {
return data.entrySet();
}
public Set> keyValueSet() {
Set> result = new LinkedHashSet<>();
for (K key : data.keySet()) {
for (V value : data.get(key)) {
result.add(new SimpleEntry<>(key, value));
}
}
return result;
}
@Override
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 set = data.get(key);
// if (set == null || set.size() == 0)
// return null;
// return set.iterator().next();
// }
public Set getAll(Object key) {
return data.get(key);
}
public Set get(Object key) {
return data.get(key);
}
@Override
public int hashCode() {
return data.hashCode();
}
public boolean isEmpty() {
return data.isEmpty();
}
public Set keySet() {
return data.keySet();
}
public V put(K key, V value) {
Set 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 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 keys, V value) {
V result = null;
for (K key : keys) {
result = put(key, value);
}
return result;
}
private Set newSet() {
try {
return 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 (Map.Entry extends K, ? extends V> entry : t.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
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 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 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 values() {
return values(new LinkedHashSet());
}
public > C values(C result) {
for (Entry> keyValue : data.entrySet()) {
result.addAll(keyValue.getValue());
}
return result;
}
@Override
public String toString() {
return data.toString();
}
static class SimpleEntry implements Entry {
K key;
V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry e) {
this.key = e.getKey();
this.value = e.getValue();
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
}
public Relation addAllInverted(Relation source) {
for (V value : source.data.keySet()) {
for (K key : source.data.get(value)) {
put(key, value);
}
}
return this;
}
public Relation addAllInverted(Map source) {
for (Map.Entry entry : source.entrySet()) {
put(entry.getValue(), entry.getKey());
}
return this;
}
volatile boolean frozen = false;
@Override
public boolean isFrozen() {
return frozen;
}
@Override
public Relation freeze() {
if (!frozen) {
// 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);
frozen = true;
}
return this;
}
@Override
public Relation cloneAsThawed() {
// TODO do later
throw new UnsupportedOperationException();
}
public boolean removeAll(Relation toBeRemoved) {
boolean result = false;
for (K key : toBeRemoved.keySet()) {
try {
Set values = toBeRemoved.getAll(key);
if (values != null) {
result |= removeAll(key, values);
}
} catch (NullPointerException e) {
// data doesn't allow null, eg ConcurrentHashMap
}
}
return result;
}
@SafeVarargs
@SuppressWarnings("varargs") // Not supported by Eclipse, but we need this for javac
public final Set removeAll(K... keys) {
return removeAll(Arrays.asList(keys));
}
public boolean removeAll(K key, Iterable toBeRemoved) {
boolean result = false;
for (V value : toBeRemoved) {
result |= remove(key, value);
}
return result;
}
public Set removeAll(Collection toBeRemoved) {
Set result = new LinkedHashSet<>();
for (K key : toBeRemoved) {
try {
final Set removals = data.remove(key);
if (removals != null) {
result.addAll(removals);
}
} catch (NullPointerException e) {
// data doesn't allow null, eg ConcurrentHashMap
}
}
return result;
}
}