1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.harmony.luni.util; 19 20 import java.util.AbstractCollection; 21 import java.util.AbstractMap; 22 import java.util.AbstractSet; 23 import java.util.Arrays; 24 import java.util.Collection; 25 import java.util.ConcurrentModificationException; 26 import java.util.Iterator; 27 import java.util.Map; 28 import java.util.NoSuchElementException; 29 import java.util.Set; 30 31 /** 32 * 33 * Reductive hash with two keys 34 * 35 */ 36 public class TwoKeyHashMap<E, K, V> extends AbstractMap<String, V> { 37 38 static final float DEFAULT_LOAD_FACTOR = 0.75f; 39 static final int DEFAULT_INITIAL_SIZE = 16; 40 41 private Set<Map.Entry<String, V>> entrySet; 42 private Collection<V> values; 43 private int size; 44 private int arrSize; 45 private int modCount; 46 47 private Entry<E, K, V>[] arr; 48 49 private float loadFactor; 50 int threshold = 0; 51 52 /** 53 * Constructs an empty HashMap 54 */ 55 public TwoKeyHashMap() { 56 this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR); 57 } 58 59 /** 60 * Constructs an empty HashMap 61 * 62 * @param initialCapacity 63 */ 64 public TwoKeyHashMap(int initialCapacity) { 65 this(initialCapacity, DEFAULT_LOAD_FACTOR); 66 } 67 68 /** 69 * Constructs an empty HashMap 70 * 71 * @param initialCapacity 72 * @param initialLoadFactor 73 */ 74 @SuppressWarnings("unchecked") 75 public TwoKeyHashMap(int initialCapacity, float initialLoadFactor) { 76 if (initialCapacity < 0) { 77 throw new IllegalArgumentException("initialCapacity should be >= 0"); 78 } 79 if (initialLoadFactor <= 0) { 80 throw new IllegalArgumentException( 81 "initialLoadFactor should be > 0"); 82 } 83 loadFactor = initialLoadFactor; 84 if (initialCapacity == Integer.MAX_VALUE) { 85 initialCapacity--; 86 } 87 arrSize = initialCapacity > 0 ? initialCapacity : 1; 88 threshold = (int) (arrSize * loadFactor); 89 arr = new Entry[arrSize + 1]; 90 } 91 92 /** 93 * Returns a collection view of the values 94 */ 95 public Collection<V> values() { 96 if (values == null) { 97 values = new ValuesCollectionImpl(); 98 } 99 return values; 100 } 101 102 /** 103 * Returns a collection view of the mappings 104 */ 105 public Set<Map.Entry<String, V>> entrySet() { 106 if (entrySet == null) { 107 entrySet = new EntrySetImpl(); 108 } 109 return entrySet; 110 } 111 112 /** 113 * Clears the map 114 */ 115 public void clear() { 116 modCount++; 117 size = 0; 118 Arrays.fill(arr, 0, arr.length, null); 119 } 120 121 /** 122 * Removes the mapping for the keys 123 * 124 * @param key1 125 * @param key2 126 * @return 127 */ 128 public V remove(Object key1, Object key2) { 129 Entry<E, K, V> e = removeEntry(key1, key2); 130 return null != e ? e.value : null; 131 } 132 133 /** 134 * Associates the specified value with the specified keys in this map 135 * 136 * @param key1 137 * @param key2 138 * @param value 139 * @return 140 */ 141 public V put(E key1, K key2, V value) { 142 if (key1 == null && key2 == null) { 143 int index = arrSize; 144 if (arr[index] == null) { 145 arr[index] = createEntry(0, null, null, value, null); 146 size++; 147 modCount++; 148 return null; 149 } else { 150 V oldValue = arr[index].value; 151 arr[index].value = value; 152 return oldValue; 153 } 154 } 155 156 int hash = key1.hashCode() + key2.hashCode(); 157 int index = (hash & 0x7fffffff) % arrSize; 158 Entry<E, K, V> e = arr[index]; 159 160 while (e != null) { 161 if (hash == e.hash && key1.equals(e.getKey1()) 162 && key2.equals(e.getKey2())) { 163 V oldValue = e.value; 164 e.value = value; 165 return oldValue; 166 } 167 e = e.next; 168 } 169 170 arr[index] = createEntry(hash, key1, key2, value, arr[index]); 171 size++; 172 modCount++; 173 174 if (size > threshold) { 175 rehash(); 176 } 177 return null; 178 } 179 180 /** 181 * Rehash the map 182 * 183 */ 184 @SuppressWarnings("unchecked") 185 void rehash() { 186 int newArrSize = (arrSize + 1) * 2 + 1; 187 if (newArrSize < 0) { 188 newArrSize = Integer.MAX_VALUE - 1; 189 } 190 Entry<E, K, V>[] newArr = new Entry[newArrSize + 1]; 191 192 for (int i = 0; i < arr.length - 1; i++) { 193 Entry<E, K, V> entry = arr[i]; 194 while (entry != null) { 195 Entry<E, K, V> next = entry.next; 196 197 int newIndex = (entry.hash & 0x7fffffff) % newArrSize; 198 entry.next = newArr[newIndex]; 199 newArr[newIndex] = entry; 200 201 entry = next; 202 } 203 } 204 newArr[newArrSize] = arr[arrSize]; // move null entry 205 arrSize = newArrSize; 206 207 // The maximum array size is reached, increased loadFactor 208 // will keep array from further growing 209 if (arrSize == Integer.MAX_VALUE) { 210 loadFactor *= 10; 211 } 212 threshold = (int) (arrSize * loadFactor); 213 arr = newArr; 214 } 215 216 /** 217 * Returns true if this map contains a mapping for {@code key1} and {@code key2}. 218 */ 219 public boolean containsKey(Object key1, Object key2) { 220 return findEntry(key1, key2) != null; 221 } 222 223 /** 224 * Return the value by keys 225 * 226 * @param key1 227 * @param key2 228 * @return 229 */ 230 public V get(Object key1, Object key2) { 231 Entry<E, K, V> e = findEntry(key1, key2); 232 if (e != null) { 233 return e.value; 234 } 235 return null; 236 } 237 238 /** 239 * Returns true if this map contains no key-value mappings 240 */ 241 public boolean isEmpty() { 242 return size == 0; 243 } 244 245 /** 246 * Returns the number of mappings 247 */ 248 public int size() { 249 return size; 250 } 251 252 /** 253 * Creates new entry 254 * 255 * @param hashCode 256 * @param key1 257 * @param key2 258 * @param value 259 * @param next 260 * @return 261 */ 262 Entry<E, K, V> createEntry(int hashCode, E key1, K key2, V value, 263 Entry<E, K, V> next) { 264 return new Entry<E, K, V>(hashCode, key1, key2, value, next); 265 } 266 267 /** 268 * Creates entries iterator 269 * 270 * @return 271 */ 272 Iterator<Map.Entry<String, V>> createEntrySetIterator() { 273 return new EntryIteratorImpl(); 274 } 275 276 /** 277 * Creates values iterator 278 * 279 * @return 280 */ 281 Iterator<V> createValueCollectionIterator() { 282 return new ValueIteratorImpl(); 283 } 284 285 /** 286 * Entry implementation for the TwoKeyHashMap class 287 * 288 */ 289 public static class Entry<E, K, V> implements Map.Entry<String, V> { 290 int hash; 291 E key1; 292 K key2; 293 V value; 294 Entry<E, K, V> next; 295 296 public Entry(int hash, E key1, K key2, V value, Entry<E, K, V> next) { 297 this.hash = hash; 298 this.key1 = key1; 299 this.key2 = key2; 300 this.value = value; 301 this.next = next; 302 } 303 304 public String getKey() { 305 return key1.toString() + key2.toString(); 306 } 307 308 public E getKey1() { 309 return key1; 310 } 311 312 public K getKey2() { 313 return key2; 314 } 315 316 public V getValue() { 317 return value; 318 } 319 320 public V setValue(V value) { 321 V oldValue = this.value; 322 this.value = value; 323 return oldValue; 324 } 325 326 public boolean equals(Object obj) { 327 if (!(obj instanceof Entry)) { 328 return false; 329 } 330 331 Entry<?, ?, ?> e = (Entry<?, ?, ?>) obj; 332 Object getKey1 = e.getKey1(); 333 Object getKey2 = e.getKey2(); 334 Object getValue = e.getValue(); 335 if ((key1 == null && getKey1 != null) 336 || (key2 == null && getKey2 != null) 337 || (value == null && getValue != null) 338 || !key1.equals(e.getKey1()) || !key2.equals(e.getKey2()) 339 || !value.equals(getValue)) { 340 return false; 341 } 342 return true; 343 } 344 345 public int hashCode() { 346 int hash1 = (key1 == null ? 0 : key1.hashCode()); 347 int hash2 = (key2 == null ? 0 : key2.hashCode()); 348 return (hash1 + hash2) ^ (value == null ? 0 : value.hashCode()); 349 } 350 351 } 352 353 class EntrySetImpl extends AbstractSet<Map.Entry<String, V>> { 354 public int size() { 355 return size; 356 } 357 358 public void clear() { 359 TwoKeyHashMap.this.clear(); 360 } 361 362 public boolean isEmpty() { 363 return size == 0; 364 } 365 366 public boolean contains(Object obj) { 367 if (!(obj instanceof Entry)) { 368 return false; 369 } 370 371 Entry<?, ?, ?> entry = (Entry<?, ?, ?>) obj; 372 Entry<E, K, V> entry2 = findEntry(entry.getKey1(), entry.getKey2()); 373 if (entry2 == null) { 374 return false; 375 } 376 Object value = entry.getValue(); 377 Object value2 = entry2.getValue(); 378 return value == null ? value2 == null : value.equals(value2); 379 } 380 381 public boolean remove(Object obj) { 382 if (!(obj instanceof Entry)) { 383 return false; 384 } 385 return removeEntry(((Entry) obj).getKey1(), ((Entry) obj).getKey2()) != null; 386 } 387 388 public Iterator<Map.Entry<String, V>> iterator() { 389 return createEntrySetIterator(); 390 } 391 } 392 393 // Iterates Entries inside the Map 394 class EntryIteratorImpl implements Iterator<Map.Entry<String, V>> { 395 private int startModCount; 396 private boolean found; 397 private int curr = -1; 398 private int returned_index = -1; 399 private Entry<E, K, V> curr_entry; 400 private Entry<E, K, V> returned_entry; 401 402 EntryIteratorImpl() { 403 startModCount = modCount; 404 } 405 406 public boolean hasNext() { 407 if (found) { 408 return true; 409 } 410 if (curr_entry != null) { 411 curr_entry = curr_entry.next; 412 } 413 if (curr_entry == null) { 414 for (curr++; curr < arr.length && arr[curr] == null; curr++) { 415 } 416 417 if (curr < arr.length) { 418 curr_entry = arr[curr]; 419 } 420 } 421 return found = (curr_entry != null); 422 } 423 424 public Map.Entry<String, V> next() { 425 if (modCount != startModCount) { 426 throw new ConcurrentModificationException(); 427 } 428 if (!hasNext()) { 429 throw new NoSuchElementException(); 430 } 431 432 found = false; 433 returned_index = curr; 434 returned_entry = curr_entry; 435 return (Map.Entry<String, V>) curr_entry; 436 } 437 438 public void remove() { 439 if (returned_index == -1) { 440 throw new IllegalStateException(); 441 } 442 443 if (modCount != startModCount) { 444 throw new ConcurrentModificationException(); 445 } 446 447 Entry<E, K, V> p = null; 448 Entry<E, K, V> e = arr[returned_index]; 449 while (e != returned_entry) { 450 p = e; 451 e = e.next; 452 } 453 if (p != null) { 454 p.next = returned_entry.next; 455 } else { 456 arr[returned_index] = returned_entry.next; 457 } 458 size--; 459 modCount++; 460 startModCount++; 461 returned_index = -1; 462 } 463 } 464 465 private final Entry<E, K, V> findEntry(Object key1, Object key2) { 466 if (key1 == null && key2 == null) { 467 return arr[arrSize]; 468 } 469 470 int hash = key1.hashCode() + key2.hashCode(); 471 int index = (hash & 0x7fffffff) % arrSize; 472 Entry<E, K, V> e = arr[index]; 473 474 while (e != null) { 475 if (hash == e.hash && key1.equals(e.getKey1()) 476 && key2.equals(e.getKey2())) { 477 return e; 478 } 479 e = e.next; 480 } 481 return null; 482 } 483 484 // Removes entry 485 private final Entry<E, K, V> removeEntry(Object key1, Object key2) { 486 if (key1 == null && key2 == null) { 487 int index = arrSize; 488 if (arr[index] != null) { 489 Entry<E, K, V> ret = arr[index]; 490 arr[index] = null; 491 size--; 492 modCount++; 493 return ret; 494 } 495 return null; 496 } 497 498 int hash = key1.hashCode() + key2.hashCode(); 499 int index = (hash & 0x7fffffff) % arrSize; 500 501 Entry<E, K, V> e = arr[index]; 502 Entry<E, K, V> prev = e; 503 while (e != null) { 504 if (hash == e.hash && key1.equals(e.getKey1()) 505 && key2.equals(e.getKey2())) { 506 if (prev == e) { 507 arr[index] = e.next; 508 } else { 509 prev.next = e.next; 510 } 511 size--; 512 modCount++; 513 return e; 514 } 515 516 prev = e; 517 e = e.next; 518 } 519 return null; 520 } 521 522 /** 523 * An instance is returned by the values() call. 524 */ 525 class ValuesCollectionImpl extends AbstractCollection<V> { 526 public int size() { 527 return size; 528 } 529 530 public void clear() { 531 TwoKeyHashMap.this.clear(); 532 } 533 534 public boolean isEmpty() { 535 return size == 0; 536 } 537 538 public Iterator<V> iterator() { 539 return createValueCollectionIterator(); 540 } 541 542 public boolean contains(Object obj) { 543 return containsValue(obj); 544 } 545 } 546 547 class ValueIteratorImpl implements Iterator<V> { 548 private EntryIteratorImpl itr; 549 550 ValueIteratorImpl() { 551 super(); 552 this.itr = new EntryIteratorImpl(); 553 } 554 555 public V next() { 556 return itr.next().getValue(); 557 } 558 559 public void remove() { 560 itr.remove(); 561 } 562 563 public boolean hasNext() { 564 return itr.hasNext(); 565 } 566 } 567 } 568