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