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