1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. 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 17package java.util; 18 19import java.io.IOException; 20import java.io.ObjectInputStream; 21import java.io.ObjectOutputStream; 22import java.io.Serializable; 23import java.lang.reflect.Array; 24 25/** 26 * An {@code Map} specialized for use with {@code Enum} types as keys. 27 */ 28public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements 29 Serializable, Cloneable, Map<K, V> { 30 31 // BEGIN android-changed 32 // added implements Map<K, V> for apicheck 33 // END android-changed 34 35 private static final long serialVersionUID = 458661240069192865L; 36 37 private Class<K> keyType; 38 39 transient Enum[] keys; 40 41 transient Object[] values; 42 43 transient boolean[] hasMapping; 44 45 private transient int mappingsCount; 46 47 transient int enumSize; 48 49 private transient EnumMapEntrySet<K, V> entrySet = null; 50 51 private static class Entry<KT extends Enum<KT>, VT> extends 52 MapEntry<KT, VT> { 53 private final EnumMap<KT, VT> enumMap; 54 55 private final int ordinal; 56 57 Entry(KT theKey, VT theValue, EnumMap<KT, VT> em) { 58 super(theKey, theValue); 59 enumMap = em; 60 ordinal = ((Enum) theKey).ordinal(); 61 } 62 63 @SuppressWarnings("unchecked") 64 @Override 65 public boolean equals(Object object) { 66 if (!enumMap.hasMapping[ordinal]) { 67 return false; 68 } 69 boolean isEqual = false; 70 if (object instanceof Map.Entry) { 71 Map.Entry<KT, VT> entry = (Map.Entry<KT, VT>) object; 72 Object enumKey = entry.getKey(); 73 if (key.equals(enumKey)) { 74 Object theValue = entry.getValue(); 75 if (enumMap.values[ordinal] == null) { 76 isEqual = (theValue == null); 77 } else { 78 isEqual = enumMap.values[ordinal].equals(theValue); 79 } 80 } 81 } 82 return isEqual; 83 } 84 85 @Override 86 public int hashCode() { 87 return (enumMap.keys[ordinal] == null ? 0 : enumMap.keys[ordinal] 88 .hashCode()) 89 ^ (enumMap.values[ordinal] == null ? 0 90 : enumMap.values[ordinal].hashCode()); 91 } 92 93 @SuppressWarnings("unchecked") 94 @Override 95 public KT getKey() { 96 checkEntryStatus(); 97 return (KT) enumMap.keys[ordinal]; 98 } 99 100 @SuppressWarnings("unchecked") 101 @Override 102 public VT getValue() { 103 checkEntryStatus(); 104 return (VT) enumMap.values[ordinal]; 105 } 106 107 @SuppressWarnings("unchecked") 108 @Override 109 public VT setValue(VT value) { 110 checkEntryStatus(); 111 return enumMap.put((KT) enumMap.keys[ordinal], value); 112 } 113 114 @Override 115 public String toString() { 116 StringBuilder result = new StringBuilder(enumMap.keys[ordinal] 117 .toString()); 118 result.append("="); 119 result.append(enumMap.values[ordinal] == null 120 ? "null" : enumMap.values[ordinal].toString()); 121 return result.toString(); 122 } 123 124 private void checkEntryStatus() { 125 if (!enumMap.hasMapping[ordinal]) { 126 throw new IllegalStateException(); 127 } 128 } 129 } 130 131 private static class EnumMapIterator<E, KT extends Enum<KT>, VT> implements 132 Iterator<E> { 133 int position = 0; 134 135 int prePosition = -1; 136 137 final EnumMap<KT, VT> enumMap; 138 139 final MapEntry.Type<E, KT, VT> type; 140 141 EnumMapIterator(MapEntry.Type<E, KT, VT> value, EnumMap<KT, VT> em) { 142 enumMap = em; 143 type = value; 144 } 145 146 public boolean hasNext() { 147 int length = enumMap.enumSize; 148 for (; position < length; position++) { 149 if (enumMap.hasMapping[position]) { 150 break; 151 } 152 } 153 return position != length; 154 } 155 156 @SuppressWarnings("unchecked") 157 public E next() { 158 if (!hasNext()) { 159 throw new NoSuchElementException(); 160 } 161 prePosition = position++; 162 return type.get(new MapEntry(enumMap.keys[prePosition], 163 enumMap.values[prePosition])); 164 } 165 166 public void remove() { 167 checkStatus(); 168 if (enumMap.hasMapping[prePosition]) { 169 enumMap.remove(enumMap.keys[prePosition]); 170 } 171 prePosition = -1; 172 } 173 174 @Override 175 @SuppressWarnings("unchecked") 176 public String toString() { 177 if (-1 == prePosition) { 178 return super.toString(); 179 } 180 return type.get( 181 new MapEntry(enumMap.keys[prePosition], 182 enumMap.values[prePosition])).toString(); 183 } 184 185 private void checkStatus() { 186 if (-1 == prePosition) { 187 throw new IllegalStateException(); 188 } 189 } 190 } 191 192 private static class EnumMapKeySet<KT extends Enum<KT>, VT> extends 193 AbstractSet<KT> { 194 private final EnumMap<KT, VT> enumMap; 195 196 EnumMapKeySet(EnumMap<KT, VT> em) { 197 enumMap = em; 198 } 199 200 @Override 201 public void clear() { 202 enumMap.clear(); 203 } 204 205 @Override 206 public boolean contains(Object object) { 207 return enumMap.containsKey(object); 208 } 209 210 @Override 211 @SuppressWarnings("unchecked") 212 public Iterator iterator() { 213 return new EnumMapIterator<KT, KT, VT>( 214 new MapEntry.Type<KT, KT, VT>() { 215 public KT get(MapEntry<KT, VT> entry) { 216 return entry.key; 217 } 218 }, enumMap); 219 } 220 221 @Override 222 @SuppressWarnings("unchecked") 223 public boolean remove(Object object) { 224 if (contains(object)) { 225 enumMap.remove(object); 226 return true; 227 } 228 return false; 229 } 230 231 @Override 232 public int size() { 233 return enumMap.size(); 234 } 235 } 236 237 private static class EnumMapValueCollection<KT extends Enum<KT>, VT> 238 extends AbstractCollection<VT> { 239 private final EnumMap<KT, VT> enumMap; 240 241 EnumMapValueCollection(EnumMap<KT, VT> em) { 242 enumMap = em; 243 } 244 245 @Override 246 public void clear() { 247 enumMap.clear(); 248 } 249 250 @Override 251 public boolean contains(Object object) { 252 return enumMap.containsValue(object); 253 } 254 255 @SuppressWarnings("unchecked") 256 @Override 257 public Iterator iterator() { 258 return new EnumMapIterator<VT, KT, VT>( 259 new MapEntry.Type<VT, KT, VT>() { 260 public VT get(MapEntry<KT, VT> entry) { 261 return entry.value; 262 } 263 }, enumMap); 264 } 265 266 @Override 267 public boolean remove(Object object) { 268 if (object == null) { 269 for (int i = 0; i < enumMap.enumSize; i++) { 270 if (enumMap.hasMapping[i] && enumMap.values[i] == null) { 271 enumMap.remove(enumMap.keys[i]); 272 return true; 273 } 274 } 275 } else { 276 for (int i = 0; i < enumMap.enumSize; i++) { 277 if (enumMap.hasMapping[i] 278 && object.equals(enumMap.values[i])) { 279 enumMap.remove(enumMap.keys[i]); 280 return true; 281 } 282 } 283 } 284 return false; 285 } 286 287 @Override 288 public int size() { 289 return enumMap.size(); 290 } 291 } 292 293 private static class EnumMapEntryIterator<E, KT extends Enum<KT>, VT> 294 extends EnumMapIterator<E, KT, VT> { 295 EnumMapEntryIterator(MapEntry.Type<E, KT, VT> value, EnumMap<KT, VT> em) { 296 super(value, em); 297 } 298 299 @SuppressWarnings("unchecked") 300 @Override 301 public E next() { 302 if (!hasNext()) { 303 throw new NoSuchElementException(); 304 } 305 prePosition = position++; 306 return type.get(new Entry<KT, VT>((KT) enumMap.keys[prePosition], 307 (VT) enumMap.values[prePosition], enumMap)); 308 } 309 } 310 311 private static class EnumMapEntrySet<KT extends Enum<KT>, VT> extends 312 AbstractSet<Map.Entry<KT, VT>> { 313 private final EnumMap<KT, VT> enumMap; 314 315 EnumMapEntrySet(EnumMap<KT, VT> em) { 316 enumMap = em; 317 } 318 319 @Override 320 public void clear() { 321 enumMap.clear(); 322 } 323 324 @Override 325 public boolean contains(Object object) { 326 boolean isEqual = false; 327 if (object instanceof Map.Entry) { 328 Object enumKey = ((Map.Entry) object).getKey(); 329 Object enumValue = ((Map.Entry) object).getValue(); 330 if (enumMap.containsKey(enumKey)) { 331 VT value = enumMap.get(enumKey); 332 if (value == null) { 333 isEqual = enumValue == null; 334 } else { 335 isEqual = value.equals(enumValue); 336 } 337 } 338 } 339 return isEqual; 340 } 341 342 @Override 343 public Iterator<Map.Entry<KT, VT>> iterator() { 344 return new EnumMapEntryIterator<Map.Entry<KT, VT>, KT, VT>( 345 new MapEntry.Type<Map.Entry<KT, VT>, KT, VT>() { 346 public Map.Entry<KT, VT> get(MapEntry<KT, VT> entry) { 347 return entry; 348 } 349 }, enumMap); 350 } 351 352 @Override 353 public boolean remove(Object object) { 354 if (contains(object)) { 355 enumMap.remove(((Map.Entry) object).getKey()); 356 return true; 357 } 358 return false; 359 } 360 361 @Override 362 public int size() { 363 return enumMap.size(); 364 } 365 366 @Override 367 public Object[] toArray() { 368 Object[] entryArray = new Object[enumMap.size()]; 369 return toArray(entryArray); 370 } 371 372 @SuppressWarnings("unchecked") 373 @Override 374 public Object[] toArray(Object[] array) { 375 int size = enumMap.size(); 376 int index = 0; 377 Object[] entryArray = array; 378 if (size > array.length) { 379 Class<?> clazz = array.getClass().getComponentType(); 380 entryArray = (Object[]) Array.newInstance(clazz, size); 381 } 382 Iterator<Map.Entry<KT, VT>> iter = iterator(); 383 for (; index < size; index++) { 384 Map.Entry<KT, VT> entry = iter.next(); 385 entryArray[index] = new MapEntry<KT, VT>(entry.getKey(), entry 386 .getValue()); 387 } 388 if (index < array.length) { 389 entryArray[index] = null; 390 } 391 return entryArray; 392 } 393 } 394 395 /** 396 * Constructs an empty {@code EnumMap} using the given key type. 397 * 398 * @param keyType 399 * the class object giving the type of the keys used by this {@code EnumMap}. 400 * @throws NullPointerException 401 * if {@code keyType} is {@code null}. 402 */ 403 public EnumMap(Class<K> keyType) { 404 initialization(keyType); 405 } 406 407 /** 408 * Constructs an {@code EnumMap} using the same key type as the given {@code EnumMap} and 409 * initially containing the same mappings. 410 * 411 * @param map 412 * the {@code EnumMap} from which this {@code EnumMap} is initialized. 413 * @throws NullPointerException 414 * if {@code map} is {@code null}. 415 */ 416 public EnumMap(EnumMap<K, ? extends V> map) { 417 initialization(map); 418 } 419 420 /** 421 * Constructs an {@code EnumMap} initialized from the given map. If the given map 422 * is an {@code EnumMap} instance, this constructor behaves in the exactly the same 423 * way as {@link EnumMap#EnumMap(EnumMap)}}. Otherwise, the given map 424 * should contain at least one mapping. 425 * 426 * @param map 427 * the map from which this {@code EnumMap} is initialized. 428 * @throws IllegalArgumentException 429 * if {@code map} is not an {@code EnumMap} instance and does not contain 430 * any mappings. 431 * @throws NullPointerException 432 * if {@code map} is {@code null}. 433 */ 434 @SuppressWarnings("unchecked") 435 public EnumMap(Map<K, ? extends V> map) { 436 if (map instanceof EnumMap) { 437 initialization((EnumMap<K, V>) map); 438 } else { 439 if (map.size() == 0) { 440 throw new IllegalArgumentException(); 441 } 442 Iterator<K> iter = map.keySet().iterator(); 443 K enumKey = iter.next(); 444 Class clazz = enumKey.getClass(); 445 if (clazz.isEnum()) { 446 initialization(clazz); 447 } else { 448 initialization(clazz.getSuperclass()); 449 } 450 putAllImpl(map); 451 } 452 } 453 454 /** 455 * Removes all elements from this {@code EnumMap}, leaving it empty. 456 * 457 * @see #isEmpty() 458 * @see #size() 459 */ 460 @Override 461 public void clear() { 462 Arrays.fill(values, null); 463 Arrays.fill(hasMapping, false); 464 mappingsCount = 0; 465 } 466 467 /** 468 * Returns a shallow copy of this {@code EnumMap}. 469 * 470 * @return a shallow copy of this {@code EnumMap}. 471 */ 472 @SuppressWarnings("unchecked") 473 @Override 474 public EnumMap<K, V> clone() { 475 try { 476 EnumMap<K, V> enumMap = (EnumMap<K, V>) super.clone(); 477 enumMap.initialization(this); 478 return enumMap; 479 } catch (CloneNotSupportedException e) { 480 throw new AssertionError(e); 481 } 482 } 483 484 /** 485 * Returns whether this {@code EnumMap} contains the specified key. 486 * 487 * @param key 488 * the key to search for. 489 * @return {@code true} if this {@code EnumMap} contains the specified key, 490 * {@code false} otherwise. 491 */ 492 @Override 493 public boolean containsKey(Object key) { 494 if (isValidKeyType(key)) { 495 int keyOrdinal = ((Enum) key).ordinal(); 496 return hasMapping[keyOrdinal]; 497 } 498 return false; 499 } 500 501 /** 502 * Returns whether this {@code EnumMap} contains the specified value. 503 * 504 * @param value 505 * the value to search for. 506 * @return {@code true} if this {@code EnumMap} contains the specified value, 507 * {@code false} otherwise. 508 */ 509 @Override 510 public boolean containsValue(Object value) { 511 if (value == null) { 512 for (int i = 0; i < enumSize; i++) { 513 if (hasMapping[i] && values[i] == null) { 514 return true; 515 } 516 } 517 } else { 518 for (int i = 0; i < enumSize; i++) { 519 if (hasMapping[i] && value.equals(values[i])) { 520 return true; 521 } 522 } 523 } 524 return false; 525 } 526 527 /** 528 * Returns a {@code Set} containing all of the mappings in this {@code EnumMap}. Each mapping is 529 * an instance of {@link Map.Entry}. As the {@code Set} is backed by this {@code EnumMap}, 530 * changes in one will be reflected in the other. 531 * <p> 532 * The order of the entries in the set will be the order that the enum keys 533 * were declared in. 534 * 535 * @return a {@code Set} of the mappings. 536 */ 537 @Override 538 public Set<Map.Entry<K, V>> entrySet() { 539 if (entrySet == null) { 540 entrySet = new EnumMapEntrySet<K, V>(this); 541 } 542 return entrySet; 543 } 544 545 /** 546 * Compares the argument to the receiver, and returns {@code true} if the 547 * specified {@code Object} is an {@code EnumMap} and both {@code EnumMap}s contain the same mappings. 548 * 549 * @param object 550 * the {@code Object} to compare with this {@code EnumMap}. 551 * @return boolean {@code true} if {@code object} is the same as this {@code EnumMap}, 552 * {@code false} otherwise. 553 * @see #hashCode() 554 * @see #entrySet() 555 */ 556 @SuppressWarnings("unchecked") 557 @Override 558 public boolean equals(Object object) { 559 if (this == object) { 560 return true; 561 } 562 if (!(object instanceof EnumMap)) { 563 return super.equals(object); 564 } 565 EnumMap<K, V> enumMap = (EnumMap<K, V>) object; 566 if (keyType != enumMap.keyType || size() != enumMap.size()) { 567 return false; 568 } 569 return Arrays.equals(hasMapping, enumMap.hasMapping) 570 && Arrays.equals(values, enumMap.values); 571 } 572 573 /** 574 * Returns the value of the mapping with the specified key. 575 * 576 * @param key 577 * the key. 578 * @return the value of the mapping with the specified key, or {@code null} 579 * if no mapping for the specified key is found. 580 */ 581 @Override 582 @SuppressWarnings("unchecked") 583 public V get(Object key) { 584 if (!isValidKeyType(key)) { 585 return null; 586 } 587 int keyOrdinal = ((Enum) key).ordinal(); 588 return (V) values[keyOrdinal]; 589 } 590 591 /** 592 * Returns a set of the keys contained in this {@code EnumMap}. The {@code Set} is backed by 593 * this {@code EnumMap} so changes to one are reflected in the other. The {@code Set} does not 594 * support adding. 595 * <p> 596 * The order of the set will be the order that the enum keys were declared 597 * in. 598 * 599 * @return a {@code Set} of the keys. 600 */ 601 @Override 602 public Set<K> keySet() { 603 if (keySet == null) { 604 keySet = new EnumMapKeySet<K, V>(this); 605 } 606 return keySet; 607 } 608 609 /** 610 * Maps the specified key to the specified value. 611 * 612 * @param key 613 * the key. 614 * @param value 615 * the value. 616 * @return the value of any previous mapping with the specified key or 617 * {@code null} if there was no mapping. 618 * @throws UnsupportedOperationException 619 * if adding to this map is not supported. 620 * @throws ClassCastException 621 * if the class of the key or value is inappropriate for this 622 * map. 623 * @throws IllegalArgumentException 624 * if the key or value cannot be added to this map. 625 * @throws NullPointerException 626 * if the key or value is {@code null} and this {@code EnumMap} does not 627 * support {@code null} keys or values. 628 */ 629 @Override 630 @SuppressWarnings("unchecked") 631 public V put(K key, V value) { 632 return putImpl(key, value); 633 } 634 635 /** 636 * Copies every mapping in the specified {@code Map} to this {@code EnumMap}. 637 * 638 * @param map 639 * the {@code Map} to copy mappings from. 640 * @throws UnsupportedOperationException 641 * if adding to this {@code EnumMap} is not supported. 642 * @throws ClassCastException 643 * if the class of a key or value is inappropriate for this 644 * {@code EnumMap}. 645 * @throws IllegalArgumentException 646 * if a key or value cannot be added to this map. 647 * @throws NullPointerException 648 * if a key or value is {@code null} and this {@code EnumMap} does not 649 * support {@code null} keys or values. 650 */ 651 @Override 652 @SuppressWarnings("unchecked") 653 public void putAll(Map<? extends K, ? extends V> map) { 654 putAllImpl(map); 655 } 656 657 /** 658 * Removes a mapping with the specified key from this {@code EnumMap}. 659 * 660 * @param key 661 * the key of the mapping to remove. 662 * @return the value of the removed mapping or {@code null} if no mapping 663 * for the specified key was found. 664 * @throws UnsupportedOperationException 665 * if removing from this {@code EnumMap} is not supported. 666 */ 667 @Override 668 @SuppressWarnings("unchecked") 669 public V remove(Object key) { 670 if (!isValidKeyType(key)) { 671 return null; 672 } 673 int keyOrdinal = ((Enum) key).ordinal(); 674 if (hasMapping[keyOrdinal]) { 675 hasMapping[keyOrdinal] = false; 676 mappingsCount--; 677 } 678 V oldValue = (V) values[keyOrdinal]; 679 values[keyOrdinal] = null; 680 return oldValue; 681 } 682 683 /** 684 * Returns the number of elements in this {@code EnumMap}. 685 * 686 * @return the number of elements in this {@code EnumMap}. 687 */ 688 @Override 689 public int size() { 690 return mappingsCount; 691 } 692 693 /** 694 * Returns a {@code Collection} of the values contained in this {@code EnumMap}. The returned 695 * {@code Collection} complies with the general rule specified in 696 * {@link Map#values()}. The {@code Collection}'s {@code Iterator} will return the values 697 * in the their corresponding keys' natural order (the {@code Enum} constants are 698 * declared in this order). 699 * <p> 700 * The order of the values in the collection will be the order that their 701 * corresponding enum keys were declared in. 702 * 703 * @return a collection of the values contained in this {@code EnumMap}. 704 */ 705 @Override 706 public Collection<V> values() { 707 if (valuesCollection == null) { 708 valuesCollection = new EnumMapValueCollection<K, V>(this); 709 } 710 return valuesCollection; 711 } 712 713 @SuppressWarnings("unchecked") 714 private void readObject(ObjectInputStream stream) throws IOException, 715 ClassNotFoundException { 716 stream.defaultReadObject(); 717 initialization(keyType); 718 int elementCount = stream.readInt(); 719 Enum<K> enumKey; 720 Object value; 721 for (int i = elementCount; i > 0; i--) { 722 enumKey = (Enum<K>) stream.readObject(); 723 value = stream.readObject(); 724 putImpl((K) enumKey, (V) value); 725 } 726 } 727 728 private void writeObject(ObjectOutputStream stream) throws IOException { 729 stream.defaultWriteObject(); 730 stream.writeInt(mappingsCount); 731 Iterator<Map.Entry<K, V>> iterator = entrySet().iterator(); 732 while (iterator.hasNext()) { 733 Map.Entry<K, V> entry = iterator.next(); 734 stream.writeObject(entry.getKey()); 735 stream.writeObject(entry.getValue()); 736 } 737 } 738 739 private boolean isValidKeyType(Object key) { 740 if (key != null && keyType.isInstance(key)) { 741 return true; 742 } 743 return false; 744 } 745 746 @SuppressWarnings("unchecked") 747 private void initialization(EnumMap enumMap) { 748 keyType = enumMap.keyType; 749 keys = enumMap.keys; 750 enumSize = enumMap.enumSize; 751 values = enumMap.values.clone(); 752 hasMapping = enumMap.hasMapping.clone(); 753 mappingsCount = enumMap.mappingsCount; 754 } 755 756 private void initialization(Class<K> type) { 757 keyType = type; 758 keys = Enum.getSharedConstants(keyType); 759 enumSize = keys.length; 760 values = new Object[enumSize]; 761 hasMapping = new boolean[enumSize]; 762 } 763 764 @SuppressWarnings("unchecked") 765 private void putAllImpl(Map map) { 766 Iterator iter = map.entrySet().iterator(); 767 while (iter.hasNext()) { 768 Map.Entry entry = (Map.Entry) iter.next(); 769 putImpl((K) entry.getKey(), (V) entry.getValue()); 770 } 771 } 772 773 @SuppressWarnings("unchecked") 774 private V putImpl(K key, V value) { 775 if (key == null) { 776 throw new NullPointerException("key == null"); 777 } 778 keyType.cast(key); // Called to throw ClassCastException. 779 int keyOrdinal = key.ordinal(); 780 if (!hasMapping[keyOrdinal]) { 781 hasMapping[keyOrdinal] = true; 782 mappingsCount++; 783 } 784 V oldValue = (V) values[keyOrdinal]; 785 values[keyOrdinal] = value; 786 return oldValue; 787 } 788 789} 790