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}