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