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