001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.collections; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026/** 027 * A 2-dimensional hash map. Allows storage of items identified by two different 028 * keys. This can be used to store the following data structure: 029 * 030 * <ul> 031 * <li>Project A 032 * <ul> 033 * <li>Dan — <b>Testing </b></li> 034 * <li>Flo — <b>Documentation </b></li> 035 * </ul> 036 * </li> 037 * <li>Project B 038 * <ul> 039 * <li>Flo — <b>Design </b></li> 040 * <li>Dan — <b>QA </b></li> 041 * <li>Markus — <b>CM </b></li> 042 * <li>Jorge — <b>Testing </b></li> 043 * </ul> 044 * </li> 045 * </ul> 046 */ 047public class TwoDimHashMap<K1, K2, V> { 048 049 /** The first level map. */ 050 private final Map<K1, Map<K2, V>> data; 051 052 /** Create a new doubly hashed map. */ 053 public TwoDimHashMap() { 054 data = new HashMap<K1, Map<K2, V>>(); 055 } 056 057 /** Create a new two dimensional map using the data in the given map. */ 058 public TwoDimHashMap(Map<K1, Map<K2, V>> map) { 059 data = map; 060 } 061 062 /** Put all values of another {@link TwoDimHashMap} into this map. */ 063 public void putAll(TwoDimHashMap<K1, K2, V> otherMap) { 064 for (K1 key1 : otherMap.getFirstKeys()) { 065 for (K2 key2 : otherMap.getSecondKeys(key1)) { 066 V value = otherMap.getValue(key1, key2); 067 putValue(key1, key2, value); 068 } 069 } 070 } 071 072 /** 073 * Puts the given value into this map under the given keys. Any potentially 074 * existing value will be overwritten. 075 * 076 * @param key1 077 * first level key 078 * @param key2 079 * second level key 080 * @param value 081 * the value 082 */ 083 public void putValue(K1 key1, K2 key2, V value) { 084 Map<K2, V> map = data.get(key1); 085 if (map == null) { 086 map = new HashMap<K2, V>(); 087 data.put(key1, map); 088 } 089 map.put(key2, value); 090 } 091 092 /** 093 * Get a value by specifying first and second level key. 094 * 095 * @param firstKey 096 * first level key 097 * @param secondKey 098 * second level key 099 * @return the value. Is <code>null</code> if first or second level key does 100 * not exist or if <code>null</code> was explicitly stored. 101 */ 102 public V getValue(K1 firstKey, K2 secondKey) { 103 Map<K2, V> map = data.get(firstKey); 104 if (map == null) { 105 return null; 106 } 107 return map.get(secondKey); 108 } 109 110 /** 111 * Returns whether the given key combination is available in the map. 112 * 113 * @param firstKey 114 * first level key 115 * @param secondKey 116 * second level key 117 */ 118 public boolean containsKey(K1 firstKey, K2 secondKey) { 119 Map<K2, V> map = data.get(firstKey); 120 if (map == null) { 121 return false; 122 } 123 return map.containsKey(secondKey); 124 } 125 126 /** 127 * Get all values referenced by a first level key. 128 * 129 * @param firstKey 130 * the first level key 131 * @return a list of values referenced by the specified first level key 132 */ 133 public Collection<V> getValuesByFirstKey(K1 firstKey) { 134 Map<K2, V> map = data.get(firstKey); 135 if (map == null) { 136 return null; 137 } 138 return map.values(); 139 140 } 141 142 /** 143 * Get all first level keys. 144 */ 145 public Set<K1> getFirstKeys() { 146 return data.keySet(); 147 } 148 149 /** 150 * Get all the second level keys stored under the given first level key. 151 * 152 * @param firstKey 153 * the first level key. 154 * @return all second level keys for a first level key. 155 */ 156 public Set<K2> getSecondKeys(K1 firstKey) { 157 Map<K2, V> map = data.get(firstKey); 158 if (map == null) { 159 return CollectionUtils.emptySet(); 160 } 161 return map.keySet(); 162 } 163 164 /** 165 * Get all values referenced by a second level key. 166 * 167 * <b>Note: </b> This method's complexity is linear in the number of first 168 * level keys. 169 * 170 * @param secondKey 171 * the second level key 172 * @return a new list of values referenced by the specified second level key 173 */ 174 public List<V> getValuesBySecondKey(K2 secondKey) { 175 ArrayList<V> result = new ArrayList<V>(); 176 177 for (Map<K2, V> map : data.values()) { 178 if (map.containsKey(secondKey)) { 179 result.add(map.get(secondKey)); 180 } 181 } 182 183 return result; 184 } 185 186 /** 187 * Get all values stored in the map. 188 * 189 * @return a new list of all values. 190 */ 191 public List<V> getValues() { 192 ArrayList<V> result = new ArrayList<V>(); 193 194 for (Map<K2, V> map : data.values()) { 195 result.addAll(map.values()); 196 } 197 198 return result; 199 } 200 201 /** 202 * Get size of the map. 203 * 204 * @return the number of values stored in this map. 205 */ 206 public int getSize() { 207 int size = 0; 208 for (Map<K2, V> map : data.values()) { 209 size += map.size(); 210 } 211 return size; 212 } 213 214 /** 215 * Check if the map is empty, i.e. no values are stored in it. 216 */ 217 public boolean isEmpty() { 218 return getSize() == 0; 219 } 220 221 /** 222 * Get the size of the (second) map stored for a first key. 223 * 224 * @return the size or 0 if the first level key wasn't found. 225 */ 226 public int getSecondSize(K1 key1) { 227 Map<K2, V> map = data.get(key1); 228 if (map == null) { 229 return 0; 230 } 231 return map.size(); 232 } 233 234 /** 235 * Clear the whole map. 236 */ 237 public void clear() { 238 data.clear(); 239 } 240 241 /** 242 * Removes the value associated with the given key combination. 243 * 244 * @return previous value associated with specified keys, or 245 * <code>null</code> if there was no mapping for those keys. A 246 * <code>null</code> return can also indicate that the map 247 * previously associated <code>null</code> with the specified keys. 248 */ 249 public V remove(K1 key1, K2 key2) { 250 Map<K2, V> map = data.get(key1); 251 if (map == null) { 252 return null; 253 } 254 255 if (!map.containsKey(key2)) { 256 return null; 257 } 258 259 V result = map.remove(key2); 260 261 if (map.isEmpty()) { 262 data.remove(key1); 263 } 264 265 return result; 266 } 267 268 /** 269 * Remove all values stored under the given first level key. 270 * 271 * @param key 272 * first level key 273 * @return <code>true</code> if the given key was present in the map, 274 * <code>false</code> otherwise. 275 */ 276 public boolean remove(K1 key) { 277 Map<K2, V> result = data.remove(key); 278 return result != null; 279 } 280 281 /** 282 * Returns the data stored under the given first-level key as an 283 * unmodifiable map or <code>null</code> if nothing is stored under that 284 * key. 285 */ 286 public UnmodifiableMap<K2, V> getSecondMap(K1 key) { 287 Map<K2, V> secondMap = data.get(key); 288 if (secondMap == null) { 289 return null; 290 } 291 return CollectionUtils.asUnmodifiable(secondMap); 292 } 293}