001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://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.apache.commons.collections4.map; 018 019import java.io.IOException; 020import java.io.InvalidObjectException; 021import java.io.ObjectInputStream; 022import java.io.ObjectOutputStream; 023import java.io.Serializable; 024import java.util.AbstractList; 025import java.util.AbstractSet; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.IdentityHashMap; 031import java.util.Iterator; 032import java.util.List; 033import java.util.ListIterator; 034import java.util.Map; 035import java.util.NoSuchElementException; 036import java.util.Set; 037 038import org.apache.commons.collections4.OrderedMap; 039import org.apache.commons.collections4.OrderedMapIterator; 040import org.apache.commons.collections4.ResettableIterator; 041import org.apache.commons.collections4.iterators.AbstractUntypedIteratorDecorator; 042import org.apache.commons.collections4.keyvalue.AbstractMapEntry; 043import org.apache.commons.collections4.list.UnmodifiableList; 044 045/** 046 * Decorates a {@code Map} to ensure that the order of addition is retained 047 * using a {@code List} to maintain order. 048 * <p> 049 * The order will be used via the iterators and toArray methods on the views. 050 * The order is also returned by the {@code MapIterator}. 051 * The {@code orderedMapIterator()} method accesses an iterator that can 052 * iterate both forwards and backwards through the map. 053 * In addition, non-interface methods are provided to access the map by index. 054 * </p> 055 * <p> 056 * If an object is added to the Map for a second time, it will remain in the 057 * original position in the iteration. 058 * </p> 059 * <p> 060 * <strong>Note that ListOrderedMap is not synchronized and is not thread-safe.</strong> 061 * If you wish to use this map from multiple threads concurrently, you must use 062 * appropriate synchronization. The simplest approach is to wrap this map 063 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 064 * exceptions when accessed by concurrent threads without synchronization. 065 * </p> 066 * <p> 067 * <strong>Note that ListOrderedMap doesn't work with 068 * {@link IdentityHashMap IdentityHashMap}, {@link CaseInsensitiveMap}, 069 * or similar maps that violate the general contract of {@link Map}.</strong> 070 * The {@code ListOrderedMap} (or, more precisely, the underlying {@code List}) 071 * is relying on {@link Object#equals(Object) equals()}. This is fine, as long as the 072 * decorated {@code Map} is also based on {@link Object#equals(Object) equals()}, 073 * and {@link Object#hashCode() hashCode()}, which 074 * {@link IdentityHashMap IdentityHashMap}, and 075 * {@link CaseInsensitiveMap} don't: The former uses {@code ==}, and 076 * the latter uses {@link Object#equals(Object) equals()} on a lower-cased 077 * key. 078 * </p> 079 * <p> 080 * This class is {@link Serializable} starting with Commons Collections 3.1. 081 * </p> 082 * 083 * @param <K> The type of the keys in this map 084 * @param <V> The type of the values in this map 085 * @since 3.0 086 */ 087public class ListOrderedMap<K, V> 088 extends AbstractMapDecorator<K, V> 089 implements OrderedMap<K, V>, Serializable { 090 091 static class EntrySetView<K, V> extends AbstractSet<Map.Entry<K, V>> { 092 private final ListOrderedMap<K, V> parent; 093 private final List<K> insertOrder; 094 private Set<Map.Entry<K, V>> entrySet; 095 096 EntrySetView(final ListOrderedMap<K, V> parent, final List<K> insertOrder) { 097 this.parent = parent; 098 this.insertOrder = insertOrder; 099 } 100 101 @Override 102 public void clear() { 103 parent.clear(); 104 } 105 106 @Override 107 public boolean contains(final Object obj) { 108 return getEntrySet().contains(obj); 109 } 110 @Override 111 public boolean containsAll(final Collection<?> coll) { 112 return getEntrySet().containsAll(coll); 113 } 114 115 @Override 116 public boolean equals(final Object obj) { 117 if (obj == this) { 118 return true; 119 } 120 return getEntrySet().equals(obj); 121 } 122 123 private Set<Map.Entry<K, V>> getEntrySet() { 124 if (entrySet == null) { 125 entrySet = parent.decorated().entrySet(); 126 } 127 return entrySet; 128 } 129 130 @Override 131 public int hashCode() { 132 return getEntrySet().hashCode(); 133 } 134 135 @Override 136 public boolean isEmpty() { 137 return parent.isEmpty(); 138 } 139 140 @Override 141 public Iterator<Map.Entry<K, V>> iterator() { 142 return new ListOrderedIterator<>(parent, insertOrder); 143 } 144 145 @Override 146 @SuppressWarnings("unchecked") 147 public boolean remove(final Object obj) { 148 if (!(obj instanceof Map.Entry)) { 149 return false; 150 } 151 if (getEntrySet().contains(obj)) { 152 final Object key = ((Map.Entry<K, V>) obj).getKey(); 153 parent.remove(key); 154 return true; 155 } 156 return false; 157 } 158 159 @Override 160 public int size() { 161 return parent.size(); 162 } 163 164 @Override 165 public String toString() { 166 return getEntrySet().toString(); 167 } 168 } 169 170 static class KeySetView<K> extends AbstractSet<K> { 171 private final ListOrderedMap<K, Object> parent; 172 173 @SuppressWarnings("unchecked") 174 KeySetView(final ListOrderedMap<K, ?> parent) { 175 this.parent = (ListOrderedMap<K, Object>) parent; 176 } 177 178 @Override 179 public void clear() { 180 parent.clear(); 181 } 182 183 @Override 184 public boolean contains(final Object value) { 185 return parent.containsKey(value); 186 } 187 188 @Override 189 public Iterator<K> iterator() { 190 return new AbstractUntypedIteratorDecorator<Map.Entry<K, Object>, K>(parent.entrySet().iterator()) { 191 @Override 192 public K next() { 193 return getIterator().next().getKey(); 194 } 195 }; 196 } 197 198 @Override 199 public int size() { 200 return parent.size(); 201 } 202 } 203 204 static class ListOrderedIterator<K, V> extends AbstractUntypedIteratorDecorator<K, Map.Entry<K, V>> { 205 private final ListOrderedMap<K, V> parent; 206 private K last; 207 208 ListOrderedIterator(final ListOrderedMap<K, V> parent, final List<K> insertOrder) { 209 super(insertOrder.iterator()); 210 this.parent = parent; 211 } 212 213 @Override 214 public Map.Entry<K, V> next() { 215 last = getIterator().next(); 216 return new ListOrderedMapEntry<>(parent, last); 217 } 218 219 @Override 220 public void remove() { 221 super.remove(); 222 parent.decorated().remove(last); 223 } 224 } 225 226 static class ListOrderedMapEntry<K, V> extends AbstractMapEntry<K, V> { 227 private final ListOrderedMap<K, V> parent; 228 229 ListOrderedMapEntry(final ListOrderedMap<K, V> parent, final K key) { 230 super(key, null); 231 this.parent = parent; 232 } 233 234 @Override 235 public V getValue() { 236 return parent.get(getKey()); 237 } 238 239 @Override 240 public V setValue(final V value) { 241 return parent.decorated().put(getKey(), value); 242 } 243 } 244 245 static class ListOrderedMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> { 246 private final ListOrderedMap<K, V> parent; 247 private ListIterator<K> iterator; 248 private K last; 249 private boolean readable; 250 251 ListOrderedMapIterator(final ListOrderedMap<K, V> parent) { 252 this.parent = parent; 253 this.iterator = parent.insertOrder.listIterator(); 254 } 255 256 @Override 257 public K getKey() { 258 if (!readable) { 259 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); 260 } 261 return last; 262 } 263 264 @Override 265 public V getValue() { 266 if (!readable) { 267 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); 268 } 269 return parent.get(last); 270 } 271 272 @Override 273 public boolean hasNext() { 274 return iterator.hasNext(); 275 } 276 277 @Override 278 public boolean hasPrevious() { 279 return iterator.hasPrevious(); 280 } 281 282 @Override 283 public K next() { 284 last = iterator.next(); 285 readable = true; 286 return last; 287 } 288 289 @Override 290 public K previous() { 291 last = iterator.previous(); 292 readable = true; 293 return last; 294 } 295 296 @Override 297 public void remove() { 298 if (!readable) { 299 throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); 300 } 301 iterator.remove(); 302 parent.map.remove(last); 303 readable = false; 304 } 305 306 @Override 307 public void reset() { 308 iterator = parent.insertOrder.listIterator(); 309 last = null; 310 readable = false; 311 } 312 313 @Override 314 public V setValue(final V value) { 315 if (!readable) { 316 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); 317 } 318 return parent.map.put(last, value); 319 } 320 321 @Override 322 public String toString() { 323 if (readable) { 324 return "Iterator[" + getKey() + "=" + getValue() + "]"; 325 } 326 return "Iterator[]"; 327 } 328 } 329 330 static class ValuesView<V> extends AbstractList<V> { 331 private final ListOrderedMap<Object, V> parent; 332 333 @SuppressWarnings("unchecked") 334 ValuesView(final ListOrderedMap<?, V> parent) { 335 this.parent = (ListOrderedMap<Object, V>) parent; 336 } 337 338 @Override 339 public void clear() { 340 parent.clear(); 341 } 342 343 @Override 344 public boolean contains(final Object value) { 345 return parent.containsValue(value); 346 } 347 348 @Override 349 public V get(final int index) { 350 return parent.getValue(index); 351 } 352 353 @Override 354 public Iterator<V> iterator() { 355 return new AbstractUntypedIteratorDecorator<Map.Entry<Object, V>, V>(parent.entrySet().iterator()) { 356 @Override 357 public V next() { 358 return getIterator().next().getValue(); 359 } 360 }; 361 } 362 363 @Override 364 public V remove(final int index) { 365 return parent.remove(index); 366 } 367 368 @Override 369 public V set(final int index, final V value) { 370 return parent.setValue(index, value); 371 } 372 373 @Override 374 public int size() { 375 return parent.size(); 376 } 377 } 378 379 /** Serialization version */ 380 private static final long serialVersionUID = 2728177751851003750L; 381 382 /** 383 * Factory method to create an ordered map. 384 * <p> 385 * An {@code ArrayList} is used to retain order. 386 * </p> 387 * 388 * @param <K> the key type 389 * @param <V> the value type 390 * @param map The map to decorate, must not be null 391 * @return A new list ordered map 392 * @throws NullPointerException if map is null 393 * @since 4.0 394 */ 395 public static <K, V> ListOrderedMap<K, V> listOrderedMap(final Map<K, V> map) { 396 return new ListOrderedMap<>(map); 397 } 398 399 /** Internal list to hold the sequence of objects */ 400 private final List<K> insertOrder = new ArrayList<>(); 401 402 /** 403 * Constructs a new empty {@code ListOrderedMap} that decorates 404 * a {@code HashMap}. 405 * 406 * @since 3.1 407 */ 408 public ListOrderedMap() { 409 this(new HashMap<>()); 410 } 411 412 /** 413 * Constructor that wraps (not copies). 414 * 415 * @param map The map to decorate, must not be null 416 * @throws NullPointerException if map is null 417 */ 418 protected ListOrderedMap(final Map<K, V> map) { 419 super(map); 420 insertOrder.addAll(decorated().keySet()); 421 } 422 423 /** 424 * Gets an unmodifiable List view of the keys which changes as the map changes. 425 * <p> 426 * The returned list is unmodifiable because changes to the values of 427 * the list (using {@link java.util.ListIterator#set(Object)}) will 428 * effectively remove the value from the list and reinsert that value at 429 * the end of the list, which is an unexpected side effect of changing the 430 * value of a list. This occurs because changing the key, changes when the 431 * mapping is added to the map and thus where it appears in the list. 432 * </p> 433 * <p> 434 * An alternative to this method is to use the better named 435 * {@link #keyList()} or {@link #keySet()}. 436 * </p> 437 * 438 * @see #keyList() 439 * @see #keySet() 440 * @return The ordered list of keys. 441 */ 442 public List<K> asList() { 443 return keyList(); 444 } 445 446 @Override 447 public void clear() { 448 decorated().clear(); 449 insertOrder.clear(); 450 } 451 452 /** 453 * Gets a view over the entries in the map. 454 * <p> 455 * The Set will be ordered by object insertion into the map. 456 * </p> 457 * 458 * @return The fully modifiable set view over the entries 459 */ 460 @Override 461 public Set<Map.Entry<K, V>> entrySet() { 462 return new EntrySetView<>(this, insertOrder); 463 } 464 465 /** 466 * Gets the first key in this map by insert order. 467 * 468 * @return The first key currently in this map 469 * @throws NoSuchElementException if this map is empty 470 */ 471 @Override 472 public K firstKey() { 473 if (isEmpty()) { 474 throw new NoSuchElementException("Map is empty"); 475 } 476 return insertOrder.get(0); 477 } 478 479 /** 480 * Gets the key at the specified index. 481 * 482 * @param index The index to retrieve 483 * @return The key at the specified index 484 * @throws IndexOutOfBoundsException if the index is invalid 485 */ 486 public K get(final int index) { 487 return insertOrder.get(index); 488 } 489 490 /** 491 * Gets the value at the specified index. 492 * 493 * @param index The index to retrieve 494 * @return The key at the specified index 495 * @throws IndexOutOfBoundsException if the index is invalid 496 */ 497 public V getValue(final int index) { 498 return get(insertOrder.get(index)); 499 } 500 501 /** 502 * Gets the index of the specified key. 503 * 504 * @param key The key to find the index of 505 * @return The index, or -1 if not found 506 */ 507 public int indexOf(final Object key) { 508 return insertOrder.indexOf(key); 509 } 510 511 /** 512 * Gets a view over the keys in the map as a List. 513 * <p> 514 * The List will be ordered by object insertion into the map. 515 * The List is unmodifiable. 516 * </p> 517 * 518 * @see #keySet() 519 * @return The unmodifiable list view over the keys 520 * @since 3.2 521 */ 522 public List<K> keyList() { 523 return UnmodifiableList.unmodifiableList(insertOrder); 524 } 525 526 /** 527 * Gets a view over the keys in the map. 528 * <p> 529 * The Collection will be ordered by object insertion into the map. 530 * </p> 531 * 532 * @see #keyList() 533 * @return The fully modifiable collection view over the keys 534 */ 535 @Override 536 public Set<K> keySet() { 537 return new KeySetView<>(this); 538 } 539 540 /** 541 * Gets the last key in this map by insert order. 542 * 543 * @return The last key currently in this map 544 * @throws NoSuchElementException if this map is empty 545 */ 546 @Override 547 public K lastKey() { 548 if (isEmpty()) { 549 throw new NoSuchElementException("Map is empty"); 550 } 551 return insertOrder.get(size() - 1); 552 } 553 554 @Override 555 public OrderedMapIterator<K, V> mapIterator() { 556 return new ListOrderedMapIterator<>(this); 557 } 558 559 /** 560 * Gets the next key to the one specified using insert order. 561 * This method performs a list search to find the key and is O(n). 562 * 563 * @param key The key to find previous for 564 * @return The next key, null if no match or at start 565 */ 566 @Override 567 public K nextKey(final Object key) { 568 final int index = insertOrder.indexOf(key); 569 if (index >= 0 && index < size() - 1) { 570 return insertOrder.get(index + 1); 571 } 572 return null; 573 } 574 575 /** 576 * Gets the previous key to the one specified using insert order. 577 * This method performs a list search to find the key and is O(n). 578 * 579 * @param key The key to find previous for 580 * @return The previous key, null if no match or at start 581 */ 582 @Override 583 public K previousKey(final Object key) { 584 final int index = insertOrder.indexOf(key); 585 if (index > 0) { 586 return insertOrder.get(index - 1); 587 } 588 return null; 589 } 590 591 /** 592 * Puts a key-value mapping into the map at the specified index. 593 * <p> 594 * If the map already contains the key, then the original mapping 595 * is removed and the new mapping added at the specified index. 596 * The remove may change the effect of the index. The index is 597 * always calculated relative to the original state of the map. 598 * </p> 599 * <p> 600 * Thus, the steps are: (1) remove the existing key-value mapping, 601 * then (2) insert the new key-value mapping at the position it 602 * would have been inserted had the remove not occurred. 603 * </p> 604 * 605 * @param index The index at which the mapping should be inserted 606 * @param key The key 607 * @param value The value 608 * @return The value previously mapped to the key 609 * @throws IndexOutOfBoundsException if the index is out of range [0, size] 610 * @since 3.2 611 */ 612 public V put(int index, final K key, final V value) { 613 if (index < 0 || index > insertOrder.size()) { 614 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size()); 615 } 616 617 final Map<K, V> m = decorated(); 618 if (m.containsKey(key)) { 619 final V result = m.remove(key); 620 final int pos = insertOrder.indexOf(key); 621 insertOrder.remove(pos); 622 if (pos < index) { 623 index--; 624 } 625 insertOrder.add(index, key); 626 m.put(key, value); 627 return result; 628 } 629 insertOrder.add(index, key); 630 m.put(key, value); 631 return null; 632 } 633 634 @Override 635 public V put(final K key, final V value) { 636 if (decorated().containsKey(key)) { 637 // re-adding doesn't change order 638 return decorated().put(key, value); 639 } 640 // first add, so add to both map and list 641 final V result = decorated().put(key, value); 642 insertOrder.add(key); 643 return result; 644 } 645 646 /** 647 * Puts the values contained in a supplied Map into the Map starting at 648 * the specified index. 649 * 650 * @param index The index in the Map to start at. 651 * @param map The Map containing the entries to be added. 652 * @throws IndexOutOfBoundsException if the index is out of range [0, size] 653 */ 654 public void putAll(int index, final Map<? extends K, ? extends V> map) { 655 if (index < 0 || index > insertOrder.size()) { 656 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size()); 657 } 658 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 659 final K key = entry.getKey(); 660 final boolean contains = containsKey(key); 661 // The return value of put is null if the key did not exist OR the value was null 662 // so it cannot be used to determine whether the key was added 663 put(index, entry.getKey(), entry.getValue()); 664 if (!contains) { 665 // if no key was replaced, increment the index 666 index++; 667 } else { 668 // otherwise put the next item after the currently inserted key 669 index = indexOf(entry.getKey()) + 1; 670 } 671 } 672 } 673 674 @Override 675 public void putAll(final Map<? extends K, ? extends V> map) { 676 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 677 put(entry.getKey(), entry.getValue()); 678 } 679 } 680 681 /** 682 * Deserializes the map in using a custom routine. 683 * 684 * @param in The input stream 685 * @throws IOException Thrown if an error occurs while reading from the stream 686 * @throws ClassNotFoundException if an object read from the stream cannot be loaded 687 * @since 3.1 688 */ 689 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 690 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 691 in.defaultReadObject(); 692 map = (Map<K, V>) in.readObject(); // (1) 693 if (insertOrder.size() != map.size() || !new HashSet<>(insertOrder).equals(map.keySet())) { 694 throw new InvalidObjectException("Inconsistent ListOrderedMap deserialized: key order does not match the map keys"); 695 } 696 } 697 698 /** 699 * Removes the element at the specified index. 700 * 701 * @param index The index of the object to remove 702 * @return The removed value, or {@code null} if none existed 703 * @throws IndexOutOfBoundsException if the index is invalid 704 */ 705 public V remove(final int index) { 706 return remove(get(index)); 707 } 708 709 @Override 710 public V remove(final Object key) { 711 V result = null; 712 if (decorated().containsKey(key)) { 713 result = decorated().remove(key); 714 insertOrder.remove(key); 715 } 716 return result; 717 } 718 719 /** 720 * Sets the value at the specified index. 721 * 722 * @param index The index of the value to set 723 * @param value The new value to set 724 * @return The previous value at that index 725 * @throws IndexOutOfBoundsException if the index is invalid 726 * @since 3.2 727 */ 728 public V setValue(final int index, final V value) { 729 final K key = insertOrder.get(index); 730 return put(key, value); 731 } 732 733 /** 734 * Returns the Map as a string. 735 * 736 * @return The Map as a String 737 */ 738 @Override 739 public String toString() { 740 if (isEmpty()) { 741 return "{}"; 742 } 743 final StringBuilder buf = new StringBuilder(); 744 buf.append('{'); 745 boolean first = true; 746 for (final Map.Entry<K, V> entry : entrySet()) { 747 final K key = entry.getKey(); 748 final V value = entry.getValue(); 749 if (first) { 750 first = false; 751 } else { 752 buf.append(", "); 753 } 754 buf.append(key == this ? "(this Map)" : key); 755 buf.append('='); 756 buf.append(value == this ? "(this Map)" : value); 757 } 758 buf.append('}'); 759 return buf.toString(); 760 } 761 762 /** 763 * Gets a view over the values in the map as a List. 764 * <p> 765 * The List will be ordered by object insertion into the map. 766 * The List supports remove and set, but does not support add. 767 * </p> 768 * 769 * @see #values() 770 * @return The partially modifiable list view over the values 771 * @since 3.2 772 */ 773 public List<V> valueList() { 774 return new ValuesView<>(this); 775 } 776 777 /** 778 * Gets a view over the values in the map. 779 * <p> 780 * The Collection will be ordered by object insertion into the map. 781 * </p> 782 * <p> 783 * From Commons Collections 3.2, this Collection can be cast 784 * to a list, see {@link #valueList()} 785 * </p> 786 * 787 * @see #valueList() 788 * @return The fully modifiable collection view over the values 789 */ 790 @Override 791 public Collection<V> values() { 792 return new ValuesView<>(this); 793 } 794 795 /** 796 * Serializes this object to an ObjectOutputStream. 797 * 798 * @param out The target ObjectOutputStream. 799 * @throws IOException thrown when an I/O errors occur writing to the target stream. 800 * @since 3.1 801 */ 802 private void writeObject(final ObjectOutputStream out) throws IOException { 803 out.defaultWriteObject(); 804 out.writeObject(map); 805 } 806 807}