diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/collect/ImmutableSortedKeyListMultimap.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/collect/ImmutableSortedKeyListMultimap.java | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/collect/ImmutableSortedKeyListMultimap.java b/src/main/java/com/google/devtools/build/lib/collect/ImmutableSortedKeyListMultimap.java new file mode 100644 index 0000000000..216337813c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/collect/ImmutableSortedKeyListMultimap.java @@ -0,0 +1,394 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.collect; + +import com.google.common.base.Preconditions; +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; + +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A immutable multimap implementation for multimaps with comparable keys. It uses a sorted array + * and binary search to return the correct values. It's only purpose is to save memory - it consumes + * only about half the memory of the equivalent ImmutableListMultimap. Only a few methods are + * efficiently implemented: {@link #isEmpty} is O(1), {@link #get} and {@link #containsKey} are + * O(log(n)), and {@link #asMap} and {@link #values} refer to the parent instance. All other methods + * can take O(n) or even make a copy of the contents. + * + * <p>This implementation supports neither {@code null} keys nor {@code null} values. + */ +public final class ImmutableSortedKeyListMultimap<K extends Comparable<K>, V> + implements ListMultimap<K, V> { + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final ImmutableSortedKeyListMultimap EMPTY_MULTIMAP = + new ImmutableSortedKeyListMultimap(new Comparable<?>[0], new List<?>[0]); + + /** Returns the empty multimap. */ + @SuppressWarnings("unchecked") + public static <K extends Comparable<K>, V> ImmutableSortedKeyListMultimap<K, V> of() { + // Safe because the multimap will never hold any elements. + return EMPTY_MULTIMAP; + } + + @SuppressWarnings("unchecked") + public static <K extends Comparable<K>, V> ImmutableSortedKeyListMultimap<K, V> copyOf( + Multimap<K, V> data) { + if (data.isEmpty()) { + return EMPTY_MULTIMAP; + } + if (data instanceof ImmutableSortedKeyListMultimap) { + return (ImmutableSortedKeyListMultimap<K, V>) data; + } + Set<K> keySet = data.keySet(); + int size = keySet.size(); + K[] sortedKeys = (K[]) new Comparable<?>[size]; + int index = 0; + for (K key : keySet) { + sortedKeys[index++] = Preconditions.checkNotNull(key); + } + Arrays.sort(sortedKeys); + List<V>[] values = (List<V>[]) new List<?>[size]; + for (int i = 0; i < size; i++) { + values[i] = ImmutableList.copyOf(data.get(sortedKeys[i])); + } + return new ImmutableSortedKeyListMultimap<>(sortedKeys, values); + } + + public static <K extends Comparable<K>, V> Builder<K, V> builder() { + return new Builder<>(); + } + + /** + * A builder class for ImmutableSortedKeyListMultimap<K, V> instances. + */ + public static final class Builder<K extends Comparable<K>, V> { + private final Multimap<K, V> builderMultimap = ArrayListMultimap.create(); + + Builder() { + // Not public so you must call builder() instead. + } + + public ImmutableSortedKeyListMultimap<K, V> build() { + return ImmutableSortedKeyListMultimap.copyOf(builderMultimap); + } + + public Builder<K, V> put(K key, V value) { + builderMultimap.put(Preconditions.checkNotNull(key), Preconditions.checkNotNull(value)); + return this; + } + + public Builder<K, V> putAll(K key, Collection<? extends V> values) { + Collection<V> valueList = builderMultimap.get(Preconditions.checkNotNull(key)); + for (V value : values) { + valueList.add(Preconditions.checkNotNull(value)); + } + return this; + } + + @SuppressWarnings("unchecked") + public Builder<K, V> putAll(K key, V... values) { + return putAll(Preconditions.checkNotNull(key), Arrays.asList(values)); + } + + public Builder<K, V> putAll(Multimap<? extends K, ? extends V> multimap) { + for (Map.Entry<? extends K, ? extends Collection<? extends V>> entry + : multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + } + + /** + * An implementation for the Multimap.asMap method. Note that AbstractMap already provides + * implementations for all methods except {@link #entrySet}, but we override a few here because we + * can do it much faster than the existing entrySet-based implementations. Also note that it + * inherits the type parameters K and V from the parent class. + */ + private class AsMap extends AbstractMap<K, Collection<V>> { + + AsMap() { + } + + @Override + public int size() { + return sortedKeys.length; + } + + @Override + public boolean containsKey(Object key) { + return ImmutableSortedKeyListMultimap.this.containsKey(key); + } + + @Override + public Collection<V> get(Object key) { + int index = Arrays.binarySearch(sortedKeys, key); + // Note the different semantic between Map and Multimap. + return index >= 0 ? values[index] : null; + } + + @Override + public Collection<V> remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set<Entry<K, Collection<V>>> entrySet() { + ImmutableSet.Builder<Entry<K, Collection<V>>> builder = ImmutableSet.builder(); + for (int i = 0; i < sortedKeys.length; i++) { + builder.add(new SimpleImmutableEntry<K, Collection<V>>(sortedKeys[i], values[i])); + } + return builder.build(); + } + } + + private class ValuesCollection extends AbstractCollection<V> { + + ValuesCollection() { + } + + @Override + public int size() { + return ImmutableSortedKeyListMultimap.this.size(); + } + + @Override + public boolean isEmpty() { + return sortedKeys.length == 0; + } + + @Override + public boolean contains(Object o) { + return ImmutableSortedKeyListMultimap.this.containsValue(o); + } + + @Override + public Iterator<V> iterator() { + if (isEmpty()) { + return Collections.emptyIterator(); + } + return new AbstractIterator<V>() { + private int currentList = 0; + private int currentIndex = 0; + + @Override + protected V computeNext() { + if (currentList >= values.length) { + return endOfData(); + } + V result = values[currentList].get(currentIndex); + // Find the next list/index pair. + currentIndex++; + if (currentIndex >= values[currentList].size()) { + currentIndex = 0; + currentList++; + } + return result; + } + }; + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } + + private final K[] sortedKeys; + private final List<V>[] values; + + private ImmutableSortedKeyListMultimap(K[] sortedKeys, List<V>[] values) { + this.sortedKeys = sortedKeys; + this.values = values; + } + + @Override + public int size() { + int result = 0; + for (List<V> list : values) { + result += list.size(); + } + return result; + } + + @Override + public boolean isEmpty() { + return sortedKeys.length == 0; + } + + @Override + public boolean containsKey(Object key) { + int index = Arrays.binarySearch(sortedKeys, key); + return index >= 0; + } + + @Override + public boolean containsValue(Object value) { + for (List<V> list : values) { + if (list.contains(value)) { + return true; + } + } + return false; + } + + @Override + public boolean containsEntry(Object key, Object value) { + int index = Arrays.binarySearch(sortedKeys, key); + if (index >= 0) { + return values[index].contains(value); + } + return false; + } + + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(K key, Iterable<? extends V> values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap<? extends K, ? extends V> multimap) { + throw new UnsupportedOperationException(); + } + + @Override + public List<V> replaceValues(K key, Iterable<? extends V> values) { + throw new UnsupportedOperationException(); + } + + @Override + public List<V> removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public List<V> get(K key) { + int index = Arrays.binarySearch(sortedKeys, key); + return index >= 0 ? values[index] : ImmutableList.<V>of(); + } + + @Override + public Set<K> keySet() { + return ImmutableSet.copyOf(sortedKeys); + } + + @Override + public Multiset<K> keys() { + return ImmutableMultiset.copyOf(sortedKeys); + } + + @Override + public Collection<V> values() { + return new ValuesCollection(); + } + + @Override + public Collection<Entry<K, V>> entries() { + ImmutableList.Builder<Entry<K, V>> builder = ImmutableList.builder(); + for (int i = 0; i < sortedKeys.length; i++) { + for (V value : values[i]) { + builder.add(new SimpleImmutableEntry<K, V>(sortedKeys[i], value)); + } + } + return builder.build(); + } + + /** + * {@inheritDoc} + * + * <p>Note that only {@code get} and {@code containsKey} are implemented efficiently on the + * returned map. + */ + @Override + public Map<K, Collection<V>> asMap() { + return new AsMap(); + } + + @Override + public String toString() { + return asMap().toString(); + } + + @Override + public int hashCode() { + return asMap().hashCode(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (this == object) { + return true; + } + if (object instanceof Multimap) { + Multimap<?, ?> that = (Multimap<?, ?>) object; + return asMap().equals(that.asMap()); + } + return false; + } +} |