001package ezvcard.util; 002 003import java.util.AbstractCollection; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.ConcurrentModificationException; 008import java.util.Iterator; 009import java.util.LinkedHashMap; 010import java.util.List; 011import java.util.ListIterator; 012import java.util.Map; 013import java.util.Map.Entry; 014import java.util.Set; 015 016/* 017 Copyright (c) 2012-2023, Michael Angstadt 018 All rights reserved. 019 020 Redistribution and use in source and binary forms, with or without 021 modification, are permitted provided that the following conditions are met: 022 023 1. Redistributions of source code must retain the above copyright notice, this 024 list of conditions and the following disclaimer. 025 2. Redistributions in binary form must reproduce the above copyright notice, 026 this list of conditions and the following disclaimer in the documentation 027 and/or other materials provided with the distribution. 028 029 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 030 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 031 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 032 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 033 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 034 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 035 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 036 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 037 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 038 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 039 040 The views and conclusions contained in the software and documentation are those 041 of the authors and should not be interpreted as representing official policies, 042 either expressed or implied, of the FreeBSD Project. 043 */ 044 045/* 046 * Copyright (C) 2007 The Guava Authors 047 * 048 * Licensed under the Apache License, Version 2.0 (the "License"); 049 * you may not use this file except in compliance with the License. 050 * You may obtain a copy of the License at 051 * 052 * http://www.apache.org/licenses/LICENSE-2.0 053 * 054 * Unless required by applicable law or agreed to in writing, software 055 * distributed under the License is distributed on an "AS IS" BASIS, 056 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 057 * See the License for the specific language governing permissions and 058 * limitations under the License. 059 */ 060 061/** 062 * A multimap that uses {@link ArrayList} objects to store its values. The 063 * internal {@link Map} implementation is a {@link LinkedHashMap}. 064 * @author Michael Angstadt 065 * @param <K> the key 066 * @param <V> the value 067 */ 068public class ListMultimap<K, V> implements Iterable<Map.Entry<K, List<V>>> { 069 private final Map<K, List<V>> map; 070 071 /** 072 * Creates an empty multimap. 073 */ 074 public ListMultimap() { 075 this(new LinkedHashMap<>()); 076 } 077 078 /** 079 * Creates an empty multimap. 080 * @param initialCapacity the initial capacity of the underlying map. 081 */ 082 public ListMultimap(int initialCapacity) { 083 this(new LinkedHashMap<>(initialCapacity)); 084 } 085 086 /** 087 * Creates a copy of an existing multimap. 088 * @param orig the multimap to copy from 089 */ 090 public ListMultimap(ListMultimap<K, V> orig) { 091 this(copy(orig.map)); 092 } 093 094 private static <K, V> Map<K, List<V>> copy(Map<K, List<V>> orig) { 095 Map<K, List<V>> map = new LinkedHashMap<>(orig.size()); 096 for (Map.Entry<K, List<V>> entry : orig.entrySet()) { 097 List<V> values = new ArrayList<>(entry.getValue()); 098 map.put(entry.getKey(), values); 099 } 100 return map; 101 } 102 103 /** 104 * <p> 105 * Creates a new multimap backed by the given map. Changes made to the given 106 * map will effect the multimap and vice versa. 107 * </p> 108 * <p> 109 * To avoid problems, it is highly recommended that the given map NOT be 110 * modified by anything other than this {@link ListMultimap} class after 111 * being passed into this constructor. 112 * </p> 113 * @param map the backing map 114 */ 115 public ListMultimap(Map<K, List<V>> map) { 116 this.map = map; 117 } 118 119 /** 120 * Adds a value to the multimap. 121 * @param key the key 122 * @param value the value to add 123 */ 124 public void put(K key, V value) { 125 key = sanitizeKey(key); 126 List<V> list = map.get(key); 127 if (list == null) { 128 list = new ArrayList<>(); 129 map.put(key, list); 130 } 131 list.add(value); 132 } 133 134 /** 135 * Adds multiple values to the multimap. 136 * @param key the key 137 * @param values the values to add 138 */ 139 public void putAll(K key, Collection<V> values) { 140 if (values.isEmpty()) { 141 return; 142 } 143 144 key = sanitizeKey(key); 145 List<V> list = map.get(key); 146 if (list == null) { 147 list = new ArrayList<>(); 148 map.put(key, list); 149 } 150 list.addAll(values); 151 } 152 153 /** 154 * Gets the values associated with the key. Changes to the returned list 155 * will update the underlying multimap, and vice versa. 156 * @param key the key 157 * @return the list of values or empty list if the key doesn't exist 158 */ 159 public List<V> get(K key) { 160 key = sanitizeKey(key); 161 List<V> value = map.get(key); 162 if (value == null) { 163 value = new ArrayList<>(0); 164 } 165 return new WrappedList(key, value, null); 166 } 167 168 /** 169 * Gets the first value that's associated with a key. 170 * @param key the key 171 * @return the first value or null if the key doesn't exist 172 */ 173 public V first(K key) { 174 key = sanitizeKey(key); 175 List<V> values = map.get(key); 176 177 /* 178 * The list can be null, but never empty. Empty lists are removed from 179 * the map. 180 */ 181 return (values == null) ? null : values.get(0); 182 } 183 184 /** 185 * Determines whether the given key exists. 186 * @param key the key 187 * @return true if the key exists, false if not 188 */ 189 public boolean containsKey(K key) { 190 key = sanitizeKey(key); 191 return map.containsKey(key); 192 } 193 194 /** 195 * Removes a particular value. 196 * @param key the key 197 * @param value the value to remove 198 * @return true if the multimap contained the value, false if not 199 */ 200 public boolean remove(K key, V value) { 201 key = sanitizeKey(key); 202 List<V> values = map.get(key); 203 if (values == null) { 204 return false; 205 } 206 207 boolean success = values.remove(value); 208 if (values.isEmpty()) { 209 map.remove(key); 210 } 211 return success; 212 } 213 214 /** 215 * Removes all the values associated with a key 216 * @param key the key to remove 217 * @return the removed values or an empty list if the key doesn't exist 218 * (this list is immutable) 219 */ 220 public List<V> removeAll(K key) { 221 key = sanitizeKey(key); 222 List<V> removed = map.remove(key); 223 if (removed == null) { 224 return Collections.emptyList(); 225 } 226 227 List<V> unmodifiableCopy = Collections.unmodifiableList(new ArrayList<>(removed)); 228 removed.clear(); 229 return unmodifiableCopy; 230 } 231 232 /** 233 * Replaces all values with the given value. 234 * @param key the key 235 * @param value the value with which to replace all existing values, or null 236 * to remove all values 237 * @return the values that were replaced (this list is immutable) 238 */ 239 public List<V> replace(K key, V value) { 240 List<V> replaced = removeAll(key); 241 if (value != null) { 242 put(key, value); 243 } 244 return replaced; 245 } 246 247 /** 248 * Replaces all values with the given values. 249 * @param key the key 250 * @param values the values with which to replace all existing values 251 * @return the values that were replaced (this list is immutable) 252 */ 253 public List<V> replace(K key, Collection<V> values) { 254 List<V> replaced = removeAll(key); 255 putAll(key, values); 256 return replaced; 257 } 258 259 /** 260 * Clears all entries from the multimap. 261 */ 262 public void clear() { 263 //clear each collection to make previously returned lists empty 264 for (List<V> value : map.values()) { 265 value.clear(); 266 } 267 map.clear(); 268 } 269 270 /** 271 * Gets all the keys in the multimap. 272 * @return the keys (this set is immutable) 273 */ 274 public Set<K> keySet() { 275 return Collections.unmodifiableSet(map.keySet()); 276 } 277 278 /** 279 * Gets all the values in the multimap. 280 * @return the values (this list is immutable) 281 */ 282 public List<V> values() { 283 List<V> list = new ArrayList<>(); 284 for (List<V> value : map.values()) { 285 list.addAll(value); 286 } 287 return Collections.unmodifiableList(list); 288 } 289 290 /** 291 * Determines if the multimap is empty or not. 292 * @return true if it's empty, false if not 293 */ 294 public boolean isEmpty() { 295 return size() == 0; 296 } 297 298 /** 299 * Gets the number of values in the map. 300 * @return the number of values 301 */ 302 public int size() { 303 int size = 0; 304 for (List<V> value : map.values()) { 305 size += value.size(); 306 } 307 return size; 308 } 309 310 /** 311 * Gets an immutable view of the underlying {@link Map} object. 312 * @return an immutable map 313 */ 314 public Map<K, List<V>> asMap() { 315 Map<K, List<V>> view = new LinkedHashMap<>(map.size()); 316 for (Map.Entry<K, List<V>> entry : map.entrySet()) { 317 K key = entry.getKey(); 318 List<V> value = entry.getValue(); 319 view.put(key, Collections.unmodifiableList(value)); 320 } 321 return Collections.unmodifiableMap(view); 322 } 323 324 /** 325 * Gets the {@link Map} that backs this multimap. This method is here for 326 * performances reasons. The returned map should NOT be modified by anything 327 * other than the {@link ListMultimap} object that owns it. 328 * @return the map 329 */ 330 public Map<K, List<V>> getMap() { 331 return map; 332 } 333 334 /** 335 * Modifies a given key before it is used to interact with the internal map. 336 * This method is meant to be overridden by child classes if necessary. 337 * @param key the key 338 * @return the modified key (by default, the key is returned as-is) 339 */ 340 protected K sanitizeKey(K key) { 341 return key; 342 } 343 344 /** 345 * Gets an iterator for iterating over the entries in the map. This iterator 346 * iterates over an immutable view of the map. 347 * @return the iterator 348 */ 349 //@Override 350 public Iterator<Map.Entry<K, List<V>>> iterator() { 351 final Iterator<Map.Entry<K, List<V>>> it = map.entrySet().iterator(); 352 return new Iterator<Map.Entry<K, List<V>>>() { 353 public boolean hasNext() { 354 return it.hasNext(); 355 } 356 357 public Entry<K, List<V>> next() { 358 final Entry<K, List<V>> next = it.next(); 359 return new Entry<K, List<V>>() { 360 public K getKey() { 361 return next.getKey(); 362 } 363 364 public List<V> getValue() { 365 return Collections.unmodifiableList(next.getValue()); 366 } 367 368 public List<V> setValue(List<V> value) { 369 throw new UnsupportedOperationException(); 370 } 371 }; 372 } 373 374 public void remove() { 375 throw new UnsupportedOperationException(); 376 } 377 }; 378 } 379 380 @Override 381 public String toString() { 382 return map.toString(); 383 } 384 385 @Override 386 public int hashCode() { 387 return map.hashCode(); 388 } 389 390 @Override 391 public boolean equals(Object obj) { 392 if (this == obj) return true; 393 if (obj == null) return false; 394 if (getClass() != obj.getClass()) return false; 395 396 ListMultimap<?, ?> other = (ListMultimap<?, ?>) obj; 397 return map.equals(other.map); 398 } 399 400 /** 401 * Note: This class is a modified version of the 402 * "AbstractMapBasedMultimap.WrappedList" class from the <a 403 * href="https://github.com/google/guava">Guava</a> library. 404 * 405 * <p> 406 * Collection decorator that stays in sync with the multimap values for a 407 * key. There are two kinds of wrapped collections: full and subcollections. 408 * Both have a delegate pointing to the underlying collection class. 409 * 410 * <p> 411 * Full collections, identified by a null ancestor field, contain all 412 * multimap values for a given key. Its delegate is a value in the 413 * multimap's underlying {@link Map} whenever the delegate is non-empty. The 414 * {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} 415 * methods ensure that the {@code WrappedList} and map remain consistent. 416 * 417 * <p> 418 * A subcollection, such as a sublist, contains some of the values for a 419 * given key. Its ancestor field points to the full wrapped collection with 420 * all values for the key. The subcollection {@code refreshIfEmpty}, 421 * {@code removeIfEmpty}, and {@code addToMap} methods call the 422 * corresponding methods of the full wrapped collection. 423 */ 424 private class WrappedList extends AbstractCollection<V> implements List<V> { 425 final K key; 426 List<V> delegate; 427 final WrappedList ancestor; 428 final List<V> ancestorDelegate; 429 430 WrappedList(K key, List<V> delegate, WrappedList ancestor) { 431 this.key = key; 432 this.delegate = delegate; 433 this.ancestor = ancestor; 434 this.ancestorDelegate = (ancestor == null) ? null : ancestor.getDelegate(); 435 } 436 437 public boolean addAll(int index, Collection<? extends V> collection) { 438 if (collection.isEmpty()) { 439 return false; 440 } 441 int oldSize = size(); // calls refreshIfEmpty 442 boolean changed = getDelegate().addAll(index, collection); 443 if (changed && oldSize == 0) { 444 addToMap(); 445 } 446 return changed; 447 } 448 449 public V get(int index) { 450 refreshIfEmpty(); 451 return getDelegate().get(index); 452 } 453 454 public V set(int index, V element) { 455 refreshIfEmpty(); 456 return getDelegate().set(index, element); 457 } 458 459 public void add(int index, V element) { 460 refreshIfEmpty(); 461 boolean wasEmpty = getDelegate().isEmpty(); 462 getDelegate().add(index, element); 463 if (wasEmpty) { 464 addToMap(); 465 } 466 } 467 468 public V remove(int index) { 469 refreshIfEmpty(); 470 V value = getDelegate().remove(index); 471 removeIfEmpty(); 472 return value; 473 } 474 475 public int indexOf(Object o) { 476 refreshIfEmpty(); 477 return getDelegate().indexOf(o); 478 } 479 480 public int lastIndexOf(Object o) { 481 refreshIfEmpty(); 482 return getDelegate().lastIndexOf(o); 483 } 484 485 public ListIterator<V> listIterator() { 486 refreshIfEmpty(); 487 return new WrappedListIterator(); 488 } 489 490 public ListIterator<V> listIterator(int index) { 491 refreshIfEmpty(); 492 return new WrappedListIterator(index); 493 } 494 495 public List<V> subList(int fromIndex, int toIndex) { 496 refreshIfEmpty(); 497 return new WrappedList(getKey(), getDelegate().subList(fromIndex, toIndex), (getAncestor() == null) ? this : getAncestor()); 498 } 499 500 /** 501 * If the delegate collection is empty, but the multimap has values for 502 * the key, replace the delegate with the new collection for the key. 503 * 504 * <p> 505 * For a subcollection, refresh its ancestor and validate that the 506 * ancestor delegate hasn't changed. 507 */ 508 void refreshIfEmpty() { 509 if (ancestor != null) { 510 ancestor.refreshIfEmpty(); 511 if (ancestor.getDelegate() != ancestorDelegate) { 512 throw new ConcurrentModificationException(); 513 } 514 } else if (delegate.isEmpty()) { 515 List<V> newDelegate = map.get(key); 516 if (newDelegate != null) { 517 delegate = newDelegate; 518 } 519 } 520 } 521 522 /** 523 * If collection is empty, remove it from 524 * {@code AbstractMapBasedMultimap.this.map}. For subcollections, check 525 * whether the ancestor collection is empty. 526 */ 527 void removeIfEmpty() { 528 if (ancestor != null) { 529 ancestor.removeIfEmpty(); 530 } else if (delegate.isEmpty()) { 531 map.remove(key); 532 } 533 } 534 535 K getKey() { 536 return key; 537 } 538 539 /** 540 * Add the delegate to the map. Other {@code WrappedCollection} methods 541 * should call this method after adding elements to a previously empty 542 * collection. 543 * 544 * <p> 545 * Subcollection add the ancestor's delegate instead. 546 */ 547 void addToMap() { 548 if (ancestor != null) { 549 ancestor.addToMap(); 550 } else { 551 map.put(key, delegate); 552 } 553 } 554 555 @Override 556 public int size() { 557 refreshIfEmpty(); 558 return delegate.size(); 559 } 560 561 @Override 562 public boolean equals(Object object) { 563 if (object == this) { 564 return true; 565 } 566 refreshIfEmpty(); 567 return delegate.equals(object); 568 } 569 570 @Override 571 public int hashCode() { 572 refreshIfEmpty(); 573 return delegate.hashCode(); 574 } 575 576 @Override 577 public String toString() { 578 refreshIfEmpty(); 579 return delegate.toString(); 580 } 581 582 List<V> getDelegate() { 583 return delegate; 584 } 585 586 @Override 587 public Iterator<V> iterator() { 588 refreshIfEmpty(); 589 return new WrappedListIterator(); 590 } 591 592 @Override 593 public boolean add(V value) { 594 refreshIfEmpty(); 595 boolean wasEmpty = delegate.isEmpty(); 596 boolean changed = delegate.add(value); 597 if (changed && wasEmpty) { 598 addToMap(); 599 } 600 return changed; 601 } 602 603 WrappedList getAncestor() { 604 return ancestor; 605 } 606 607 // The following methods are provided for better performance. 608 609 @Override 610 public boolean addAll(Collection<? extends V> collection) { 611 if (collection.isEmpty()) { 612 return false; 613 } 614 int oldSize = size(); // calls refreshIfEmpty 615 boolean changed = delegate.addAll(collection); 616 if (changed && oldSize == 0) { 617 addToMap(); 618 } 619 return changed; 620 } 621 622 @Override 623 public boolean contains(Object o) { 624 refreshIfEmpty(); 625 return delegate.contains(o); 626 } 627 628 @Override 629 public boolean containsAll(Collection<?> c) { 630 refreshIfEmpty(); 631 return delegate.containsAll(c); 632 } 633 634 @Override 635 public void clear() { 636 int oldSize = size(); // calls refreshIfEmpty 637 if (oldSize == 0) { 638 return; 639 } 640 delegate.clear(); 641 removeIfEmpty(); // maybe shouldn't be removed if this is a sublist 642 } 643 644 @Override 645 public boolean remove(Object o) { 646 refreshIfEmpty(); 647 boolean changed = delegate.remove(o); 648 if (changed) { 649 removeIfEmpty(); 650 } 651 return changed; 652 } 653 654 @Override 655 public boolean removeAll(Collection<?> collection) { 656 if (collection.isEmpty()) { 657 return false; 658 } 659 refreshIfEmpty(); 660 boolean changed = delegate.removeAll(collection); 661 if (changed) { 662 removeIfEmpty(); 663 } 664 return changed; 665 } 666 667 @Override 668 public boolean retainAll(Collection<?> c) { 669 refreshIfEmpty(); 670 boolean changed = delegate.retainAll(c); 671 if (changed) { 672 removeIfEmpty(); 673 } 674 return changed; 675 } 676 677 /** ListIterator decorator. */ 678 private class WrappedListIterator implements ListIterator<V> { 679 final ListIterator<V> delegateIterator; 680 final List<V> originalDelegate = delegate; 681 682 WrappedListIterator() { 683 delegateIterator = delegate.listIterator(); 684 } 685 686 public WrappedListIterator(int index) { 687 delegateIterator = delegate.listIterator(index); 688 } 689 690 public boolean hasPrevious() { 691 return getDelegateIterator().hasPrevious(); 692 } 693 694 public V previous() { 695 return getDelegateIterator().previous(); 696 } 697 698 public int nextIndex() { 699 return getDelegateIterator().nextIndex(); 700 } 701 702 public int previousIndex() { 703 return getDelegateIterator().previousIndex(); 704 } 705 706 public void set(V value) { 707 getDelegateIterator().set(value); 708 } 709 710 public void add(V value) { 711 boolean wasEmpty = isEmpty(); 712 getDelegateIterator().add(value); 713 if (wasEmpty) { 714 addToMap(); 715 } 716 } 717 718 /** 719 * If the delegate changed since the iterator was created, the 720 * iterator is no longer valid. 721 */ 722 void validateIterator() { 723 refreshIfEmpty(); 724 if (delegate != originalDelegate) { 725 throw new ConcurrentModificationException(); 726 } 727 } 728 729 public boolean hasNext() { 730 validateIterator(); 731 return delegateIterator.hasNext(); 732 } 733 734 public V next() { 735 validateIterator(); 736 return delegateIterator.next(); 737 } 738 739 public void remove() { 740 delegateIterator.remove(); 741 removeIfEmpty(); 742 } 743 744 ListIterator<V> getDelegateIterator() { 745 validateIterator(); 746 return delegateIterator; 747 } 748 } 749 } 750}