2 **********************************************************************
3 * Copyright (c) 2002-2012, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
7 **********************************************************************
9 package com.ibm.icu.dev.util;
11 import java.lang.reflect.Constructor;
12 import java.util.Arrays;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.Comparator;
16 import java.util.HashMap;
17 import java.util.LinkedHashSet;
19 import java.util.Map.Entry;
22 import com.ibm.icu.util.Freezable;
25 * A Relation is a set of mappings from keys to values.
26 * Unlike Map, there is not guaranteed to be a single value per key.
27 * The Map-like APIs return collections for values.
31 public class Relation<K, V> implements Freezable { // TODO: add , Map<K, Collection<V>>, but requires API changes
32 private Map<K, Set<V>> data;
34 Constructor<Set<V>> setCreator;
35 Object[] setComparatorParam;
37 public static <K,V> Relation<K, V> of(Map<K, Set<V>> map, Class<?> setCreator) {
38 return new Relation(map, setCreator);
41 public static <K,V> Relation<K, V> of(Map<K, Set<V>> map, Class setCreator, Comparator<V> setComparator) {
42 return new Relation(map, setCreator, setComparator);
45 public Relation(Map<K, Set<V>> map, Class<Set<V>> setCreator) {
46 this(map, setCreator, null);
49 public Relation(Map<K, Set<V>> map, Class<Set<V>> setCreator, Comparator<V> setComparator) {
51 setComparatorParam = setComparator == null ? null : new Object[]{setComparator};
52 if (setComparator == null) {
53 this.setCreator = setCreator.getConstructor();
54 this.setCreator.newInstance(setComparatorParam); // check to make sure compiles
56 this.setCreator = setCreator.getConstructor(Comparator.class);
57 this.setCreator.newInstance(setComparatorParam); // check to make sure compiles
59 data = map == null ? new HashMap() : map;
60 } catch (Exception e) {
61 throw (RuntimeException) new IllegalArgumentException("Can't create new set").initCause(e);
69 public boolean containsKey(Object key) {
70 return data.containsKey(key);
73 public boolean containsValue(Object value) {
74 for (Set<V> values : data.values()) {
75 if (values.contains(value)) {
82 public final Set<Entry<K, V>> entrySet() {
86 public Set<Entry<K, Set<V>>> keyValuesSet() {
87 return data.entrySet();
90 public Set<Entry<K, V>> keyValueSet() {
91 Set<Entry<K, V>> result = new LinkedHashSet();
92 for (K key : data.keySet()) {
93 for (V value : data.get(key)) {
94 result.add(new SimpleEntry(key, value));
100 public boolean equals(Object o) {
103 if (o.getClass() != this.getClass())
105 return data.equals(((Relation) o).data);
108 // public V get(Object key) {
109 // Set<V> set = data.get(key);
110 // if (set == null || set.size() == 0)
112 // return set.iterator().next();
115 public Set<V> getAll(Object key) {
116 return data.get(key);
119 public Set<V> get(Object key) {
120 return data.get(key);
123 public int hashCode() {
124 return data.hashCode();
127 public boolean isEmpty() {
128 return data.isEmpty();
131 public Set<K> keySet() {
132 return data.keySet();
135 public V put(K key, V value) {
136 Set<V> set = data.get(key);
138 data.put(key, set = newSet());
144 public V putAll(K key, Collection<? extends V> values) {
145 Set<V> set = data.get(key);
147 data.put(key, set = newSet());
150 return values.size() == 0 ? null : values.iterator().next();
153 public V putAll(Collection<K> keys, V value) {
156 result = put(key, value);
161 private Set<V> newSet() {
163 return (Set<V>) setCreator.newInstance(setComparatorParam);
164 } catch (Exception e) {
165 throw (RuntimeException) new IllegalArgumentException("Can't create new set").initCause(e);
169 public void putAll(Map<? extends K, ? extends V> t) {
170 for (K key : t.keySet()) {
171 put(key, t.get(key));
175 public void putAll(Relation<? extends K, ? extends V> t) {
176 for (K key : t.keySet()) {
177 for (V value : t.getAll(key)) {
183 public Set<V> removeAll(K key) {
185 return data.remove(key);
186 } catch (NullPointerException e) {
187 return null; // data doesn't allow null, eg ConcurrentHashMap
191 public boolean remove(K key, V value) {
193 Set<V> set = data.get(key);
197 boolean result = set.remove(value);
198 if (set.size() == 0) {
202 } catch (NullPointerException e) {
203 return false; // data doesn't allow null, eg ConcurrentHashMap
211 public Set<V> values() {
212 return values(new LinkedHashSet());
215 public <C extends Collection<V>> C values(C result) {
216 for (Entry<K, Set<V>> keyValue : data.entrySet()) {
217 result.addAll(keyValue.getValue());
222 public String toString() {
223 return data.toString();
226 static class SimpleEntry<K, V> implements Entry<K, V> {
231 public SimpleEntry(K key, V value) {
236 public SimpleEntry(Entry<K, V> e) {
237 this.key = e.getKey();
238 this.value = e.getValue();
245 public V getValue() {
249 public V setValue(V value) {
250 V oldValue = this.value;
256 public Relation<K,V> addAllInverted(Relation<V,K> source) {
257 for (V value : source.data.keySet()) {
258 for (K key : source.data.get(value)) {
265 public Relation<K,V> addAllInverted(Map<V,K> source) {
266 for (V value : source.keySet()) {
267 put(source.get(value), value);
272 boolean frozen = false;
274 public boolean isFrozen() {
278 public Object freeze() {
281 // does not handle one level down, so we do that on a case-by-case basis
282 for (K key : data.keySet()) {
283 data.put(key, Collections.unmodifiableSet(data.get(key)));
286 data = Collections.unmodifiableMap(data);
291 public Object cloneAsThawed() {
293 throw new UnsupportedOperationException();
296 public boolean removeAll(Relation<K, V> toBeRemoved) {
297 boolean result = false;
298 for (K key : toBeRemoved.keySet()) {
300 Set<V> values = toBeRemoved.getAll(key);
301 if (values != null) {
302 result |= removeAll(key, values);
304 } catch (NullPointerException e) {
305 // data doesn't allow null, eg ConcurrentHashMap
311 public Set<V> removeAll(K... keys) {
312 return removeAll(Arrays.asList(keys));
315 public boolean removeAll(K key, Iterable<V> toBeRemoved) {
316 boolean result = false;
317 for (V value : toBeRemoved) {
318 result |= remove(key, value);
323 public Set<V> removeAll(Collection<K> toBeRemoved) {
324 Set<V> result = new LinkedHashSet();
325 for (K key : toBeRemoved) {
327 final Set<V> removals = data.remove(key);
328 if (removals != null) {
329 result.addAll(removals);
331 } catch (NullPointerException e) {
332 // data doesn't allow null, eg ConcurrentHashMap