1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.util; 17 18 import com.android.tradefed.build.BuildSerializedVersion; 19 20 import com.google.common.base.Objects; 21 22 import java.io.Serializable; 23 import java.util.AbstractMap; 24 import java.util.ArrayList; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.HashMap; 28 import java.util.LinkedList; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.Set; 32 33 /** A {@link Map} that supports multiple values per key. */ 34 public class MultiMap<K, V> implements Serializable { 35 36 private static final long serialVersionUID = BuildSerializedVersion.VERSION; 37 private final Map<K, List<V>> mInternalMap; 38 MultiMap()39 public MultiMap() { 40 mInternalMap = new HashMap<K, List<V>>(); 41 } 42 MultiMap(MultiMap<K, V> map)43 public MultiMap(MultiMap<K, V> map) { 44 this(); 45 for (K key : map.keySet()) { 46 List<V> value = map.get(key); 47 mInternalMap.put(key, value); 48 } 49 } 50 51 /** 52 * Clears the map. 53 */ clear()54 public void clear() { 55 mInternalMap.clear(); 56 } 57 58 /** 59 * Checks whether the map contains the specified key. 60 * 61 * @see Map#containsKey(Object) 62 */ containsKey(K key)63 public boolean containsKey(K key) { 64 return mInternalMap.containsKey(key); 65 } 66 67 /** 68 * Checks whether the map contains the specified value. 69 * 70 * @see Map#containsValue(Object) 71 */ containsValue(V value)72 public boolean containsValue(V value) { 73 for (List<V> valueList : mInternalMap.values()) { 74 if (valueList.contains(value)) { 75 return true; 76 } 77 } 78 return false; 79 } 80 81 /** 82 * Gets the list of values associated with each key. 83 */ get(K key)84 public List<V> get(K key) { 85 return mInternalMap.get(key); 86 } 87 88 /** 89 * @see Map#isEmpty() 90 */ isEmpty()91 public boolean isEmpty() { 92 return mInternalMap.isEmpty(); 93 } 94 95 /** Returns a collection of all distinct keys contained in this multimap. */ keySet()96 public Set<K> keySet() { 97 return mInternalMap.keySet(); 98 } 99 100 /** 101 * Returns a collection of all key-value pairs in this MultiMap as <code>Map.Entry</code> 102 * instances. 103 */ entries()104 public Collection<Map.Entry<K, V>> entries() { 105 ArrayList<Map.Entry<K, V>> entries = new ArrayList<>(); 106 for (K key : keySet()) { 107 for (V value : get(key)) { 108 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value)); 109 } 110 } 111 return Collections.unmodifiableList(entries); 112 } 113 114 /** 115 * Adds the value to the list associated with a key. 116 * 117 * @see Map#put(Object, Object) 118 */ put(K key, V value)119 public V put(K key, V value) { 120 List<V> valueList = mInternalMap.get(key); 121 if (valueList == null) { 122 valueList = new LinkedList<V>(); 123 mInternalMap.put(key, valueList); 124 } 125 valueList.add(value); 126 return value; 127 } 128 129 /** 130 * 131 * Adds all entries in given {@link Map} to this {@link MultiMap}. 132 */ putAll(Map<? extends K, ? extends V> m)133 public void putAll(Map<? extends K, ? extends V> m) { 134 for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { 135 put(entry.getKey(), entry.getValue()); 136 } 137 } 138 139 /** 140 * Adds all entries in given {@link MultiMap} to this {@link MultiMap}. 141 */ putAll(MultiMap<K, ? extends V> m)142 public void putAll(MultiMap<K, ? extends V> m) { 143 for (K key : m.keySet()) { 144 for (V value : m.get(key)) { 145 put(key, value); 146 } 147 } 148 } 149 150 /** 151 * Removes all values associated with the specified key. 152 */ remove(K key)153 public List<V> remove(K key) { 154 return mInternalMap.remove(key); 155 } 156 157 /** 158 * Returns the number of keys in the map 159 */ size()160 public int size() { 161 return mInternalMap.size(); 162 } 163 164 /** 165 * Returns list of all values. 166 */ values()167 public List<V> values() { 168 List<V> allValues = new LinkedList<V>(); 169 for (List<V> valueList : mInternalMap.values()) { 170 allValues.addAll(valueList); 171 } 172 return allValues; 173 } 174 175 /** 176 * Construct a new map, that contains a unique String key for each value. 177 * 178 * Current algorithm will construct unique key by appending a unique position number to 179 * key's toString() value 180 * 181 * @return a {@link Map} 182 */ getUniqueMap()183 public Map<String, V> getUniqueMap() { 184 Map<String, V> uniqueMap = new HashMap<String, V>(); 185 for (Map.Entry<K, List<V>> entry : mInternalMap.entrySet()) { 186 int count = 1; 187 for (V value : entry.getValue()) { 188 if (count == 1) { 189 addUniqueEntry(uniqueMap, entry.getKey().toString(), value); 190 } else { 191 // append unique number to key for each value 192 addUniqueEntry(uniqueMap, String.format("%s%d", entry.getKey(), count), value); 193 } 194 count++; 195 } 196 } 197 return uniqueMap; 198 } 199 200 /** 201 * Recursive method that will append characters to proposedKey until its unique. Used in case 202 * there are collisions with generated key values. 203 * 204 * @param uniqueMap 205 * @param proposedKey 206 * @param value 207 */ addUniqueEntry(Map<String, V> uniqueMap, String proposedKey, V value)208 private String addUniqueEntry(Map<String, V> uniqueMap, String proposedKey, V value) { 209 // not the most efficient algorithm, but should work 210 if (uniqueMap.containsKey(proposedKey)) { 211 return addUniqueEntry(uniqueMap, String.format("%s%s", proposedKey, "X"), value); 212 } else { 213 uniqueMap.put(proposedKey, value); 214 return proposedKey; 215 } 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override hashCode()222 public int hashCode() { 223 return mInternalMap.hashCode(); 224 } 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override equals(Object obj)230 public boolean equals(Object obj) { 231 if (this == obj) { 232 return true; 233 } 234 if (obj == null) { 235 return false; 236 } 237 if (getClass() != obj.getClass()) { 238 return false; 239 } 240 MultiMap<?, ?> other = (MultiMap<?, ?>) obj; 241 return Objects.equal(mInternalMap, other.mInternalMap); 242 } 243 } 244