2 **********************************************************************
3 * Copyright (c) 2002-2011, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
7 **********************************************************************
9 package com.ibm.icu.dev.test.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) {
184 return data.remove(key);
187 public boolean remove(K key, V value) {
188 Set<V> set = data.get(key);
189 if (set == null) return false;
190 boolean result = set.remove(value);
191 if (set.size() == 0) {
201 public Set<V> values() {
202 Set<V> result = newSet();
203 for (K key : data.keySet()) {
204 result.addAll(data.get(key));
209 public String toString() {
210 return data.toString();
213 static class SimpleEntry<K, V> implements Entry<K, V> {
218 public SimpleEntry(K key, V value) {
223 public SimpleEntry(Entry<K, V> e) {
224 this.key = e.getKey();
225 this.value = e.getValue();
232 public V getValue() {
236 public V setValue(V value) {
237 V oldValue = this.value;
243 public Relation<K,V> addAllInverted(Relation<V,K> source) {
244 for (V value : source.data.keySet()) {
245 for (K key : source.data.get(value)) {
252 public Relation<K,V> addAllInverted(Map<V,K> source) {
253 for (V value : source.keySet()) {
254 put(source.get(value), value);
259 boolean frozen = false;
261 public boolean isFrozen() {
265 public Object freeze() {
268 // does not handle one level down, so we do that on a case-by-case basis
269 for (K key : data.keySet()) {
270 data.put(key, Collections.unmodifiableSet(data.get(key)));
273 data = Collections.unmodifiableMap(data);
278 public Object cloneAsThawed() {
280 throw new UnsupportedOperationException();
283 public boolean removeAll(Relation<K, V> toBeRemoved) {
284 boolean result = false;
285 for (K key : toBeRemoved.keySet()) {
286 Set<V> values = toBeRemoved.getAll(key);
287 if (values != null) {
288 result |= removeAll(key, values);
294 public Set<V> removeAll(K... keys) {
295 return data.remove(Arrays.asList(keys));
298 public boolean removeAll(K key, Iterable<V> toBeRemoved) {
299 boolean result = false;
300 for (V value : toBeRemoved) {
301 result |= remove(key, value);
306 public Set<V> removeAll(Collection<K> toBeRemoved) {
307 Set<V> result = new LinkedHashSet();
308 for (K key : toBeRemoved) {
309 final Set<V> removals = data.remove(key);
310 if (removals != null) {
311 result.addAll(removals);