001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.tools;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.HashMap;
007import java.util.LinkedHashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.Map.Entry;
011import java.util.Set;
012
013/**
014 * MultiMap - maps keys to multiple values
015 *
016 * Corresponds to Google guava LinkedHashMultimap and Apache Collections MultiValueMap
017 * but it is an independent (simple) implementation.
018 *
019 */
020public class MultiMap<A, B> {
021
022    private final Map<A, Set<B>> map;
023
024    public MultiMap() {
025        map = new HashMap<A, Set<B>>();
026    }
027
028    public MultiMap(int capacity) {
029        map = new HashMap<A, Set<B>>(capacity);
030    }
031
032    /**
033     * Map a key to a value.
034     *
035     * Can be called multiple times with the same key, but different value.
036     */
037    public void put(A key, B value) {
038        Set<B> vals = map.get(key);
039        if (vals == null) {
040            vals = new LinkedHashSet<B>();
041            map.put(key, vals);
042        }
043        vals.add(value);
044    }
045
046    /**
047     * Put a key that maps to nothing. (Only if it is not already in the map)
048     *
049     * Afterwards containsKey(key) will return true and get(key) will return
050     * an empty Set instead of null.
051     */
052    public void putVoid(A key) {
053        if (map.containsKey(key))
054            return;
055        map.put(key, new LinkedHashSet<B>());
056    }
057
058    /**
059     * Map the key to all the given values.
060     *
061     * Adds to the mappings that are already there.
062     */
063    public void putAll(A key, Collection<B> values) {
064        Set<B> vals = map.get(key);
065        if (vals == null) {
066            vals = new LinkedHashSet<B>(values);
067            map.put(key, vals);
068        }
069        vals.addAll(values);
070    }
071
072    /**
073     * Get the keySet.
074     */
075    public Set<A> keySet() {
076        return map.keySet();
077    }
078
079    /**
080     * Returns the Set associated with the given key. Result is null if
081     * nothing has been mapped to this key.
082     *
083     * Modifications of the returned list changes the underling map,
084     * but you should better not do that.
085     */
086    public Set<B> get(A key) {
087        return map.get(key);
088    }
089
090    /**
091     * Like get, but returns an empty Set if nothing has been mapped to the key.
092     */
093    public Set<B> getValues(A key) {
094        if (!map.containsKey(key))
095            return new LinkedHashSet<B>();
096        return map.get(key);
097    }
098
099    public boolean isEmpty() {
100        return map.isEmpty();
101    }
102
103    public boolean containsKey(A key) {
104        return map.containsKey(key);
105    }
106
107    /**
108     * Returns true if the multimap contains a value for a key.
109     *
110     * @param key The key
111     * @param value The value
112     * @return true if the key contains the value
113     */
114    public boolean contains(A key, B value) {
115        Set<B> values = get(key);
116        return (values == null) ? false : values.contains(value);
117    }
118
119    public void clear() {
120        map.clear();
121    }
122
123    public Set<Entry<A, Set<B>>> entrySet() {
124        return map.entrySet();
125    }
126
127    /**
128     * Returns the number of keys.
129     */
130    public int size() {
131        return map.size();
132    }
133
134    /**
135     * Returns a collection of all value sets.
136     */
137    public Collection<Set<B>> values() {
138        return map.values();
139    }
140
141    /**
142     * Removes a cerain key=value mapping.
143     *
144     * @return true, if something was removed
145     */
146    public boolean remove(A key, B value) {
147        Set<B> values = get(key);
148        if (values != null) {
149            return values.remove(value);
150        }
151        return false;
152    }
153
154    /**
155     * Removes all mappings for a certain key.
156     */
157    public Set<B> remove(A key) {
158        return map.remove(key);
159    }
160
161    @Override
162    public String toString() {
163        List<String> entries = new ArrayList<String>(map.size());
164        for (A key : map.keySet()) {
165            entries.add(key + "->{" + Utils.join(",", map.get(key)) + "}");
166        }
167        return "(" + Utils.join(",", entries) + ")";
168    }
169}