001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.tools; 003 004 import java.util.ArrayList; 005 import java.util.Collection; 006 import java.util.HashMap; 007 import java.util.LinkedHashSet; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Map.Entry; 011 import 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 */ 020 public class MultiMap<A, B> { 021 022 private final Map<A, LinkedHashSet<B>> map; 023 024 public MultiMap() { 025 map = new HashMap<A, LinkedHashSet<B>>(); 026 } 027 028 public MultiMap(int capacity) { 029 map = new HashMap<A, LinkedHashSet<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 LinkedHashSet<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 LinkedHashSet<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 LinkedHashSet<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, LinkedHashSet<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<LinkedHashSet<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 LinkedHashSet<B> remove(A key) { 158 return map.remove(key); 159 } 160 161 public String toString() { 162 List<String> entries = new ArrayList<String>(map.size()); 163 for (A key : map.keySet()) { 164 entries.add(key + "->{" + Utils.join(",", map.get(key)) + "}"); 165 } 166 return "(" + Utils.join(",", entries) + ")"; 167 } 168 }