/*
* Copyright (C) 2009 The Guava Authors
*
* 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.common.collect;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* An implementation of {@link ImmutableTable} holding an arbitrary number of
* cells.
*
* @author Gregory Kick
*/
@GwtCompatible
abstract class RegularImmutableTable<R, C, V> extends ImmutableTable<R, C, V> {
// TODO(user): split DenseImmutableTable, SparseImmutableTable into their own classes
private final ImmutableSet<Cell<R, C, V>> cellSet;
private RegularImmutableTable(ImmutableSet<Cell<R, C, V>> cellSet) {
this.cellSet = cellSet;
}
private static final Function<Cell<Object, Object, Object>, Object>
GET_VALUE_FUNCTION =
new Function<Cell<Object, Object, Object>, Object>() {
@Override public Object apply(Cell<Object, Object, Object> from) {
return from.getValue();
}
};
@SuppressWarnings("unchecked")
private Function<Cell<R, C, V>, V> getValueFunction() {
return (Function) GET_VALUE_FUNCTION;
}
@Nullable private transient volatile ImmutableList<V> valueList;
@Override public final ImmutableCollection<V> values() {
ImmutableList<V> result = valueList;
if (result == null) {
valueList = result = ImmutableList.copyOf(
Iterables.transform(cellSet(), getValueFunction()));
}
return result;
}
@Override public final int size() {
return cellSet().size();
}
@Override public final boolean containsValue(@Nullable Object value) {
return values().contains(value);
}
@Override public final boolean isEmpty() {
return false;
}
@Override public final ImmutableSet<Cell<R, C, V>> cellSet() {
return cellSet;
}
static final <R, C, V> RegularImmutableTable<R, C, V> forCells(
List<Cell<R, C, V>> cells,
@Nullable final Comparator<? super R> rowComparator,
@Nullable final Comparator<? super C> columnComparator) {
checkNotNull(cells);
if (rowComparator != null || columnComparator != null) {
/*
* This sorting logic leads to a cellSet() ordering that may not be
* expected and that isn't documented in the Javadoc. If a row Comparator
* is provided, cellSet() iterates across the columns in the first row,
* the columns in the second row, etc. If a column Comparator is provided
* but a row Comparator isn't, cellSet() iterates across the rows in the
* first column, the rows in the second column, etc.
*/
Comparator<Cell<R, C, V>> comparator = new Comparator<Cell<R, C, V>>() {
@Override public int compare(Cell<R, C, V> cell1, Cell<R, C, V> cell2) {
int rowCompare = (rowComparator == null) ? 0
: rowComparator.compare(cell1.getRowKey(), cell2.getRowKey());
if (rowCompare != 0) {
return rowCompare;
}
return (columnComparator == null) ? 0
: columnComparator.compare(
cell1.getColumnKey(), cell2.getColumnKey());
}
};
Collections.sort(cells, comparator);
}
return forCellsInternal(cells, rowComparator, columnComparator);
}
static final <R, C, V> RegularImmutableTable<R, C, V> forCells(
Iterable<Cell<R, C, V>> cells) {
return forCellsInternal(cells, null, null);
}
/**
* A factory that chooses the most space-efficient representation of the
* table.
*/
private static final <R, C, V> RegularImmutableTable<R, C, V>
forCellsInternal(Iterable<Cell<R, C, V>> cells,
@Nullable Comparator<? super R> rowComparator,
@Nullable Comparator<? super C> columnComparator) {
ImmutableSet.Builder<Cell<R, C, V>> cellSetBuilder = ImmutableSet.builder();
ImmutableSet.Builder<R> rowSpaceBuilder = ImmutableSet.builder();
ImmutableSet.Builder<C> columnSpaceBuilder = ImmutableSet.builder();
for (Cell<R, C, V> cell : cells) {
cellSetBuilder.add(cell);
rowSpaceBuilder.add(cell.getRowKey());
columnSpaceBuilder.add(cell.getColumnKey());
}
ImmutableSet<Cell<R, C, V>> cellSet = cellSetBuilder.build();
ImmutableSet<R> rowSpace = rowSpaceBuilder.build();
if (rowComparator != null) {
List<R> rowList = Lists.newArrayList(rowSpace);
Collections.sort(rowList, rowComparator);
rowSpace = ImmutableSet.copyOf(rowList);
}
ImmutableSet<C> columnSpace = columnSpaceBuilder.build();
if (columnComparator != null) {
List<C> columnList = Lists.newArrayList(columnSpace);
Collections.sort(columnList, columnComparator);
columnSpace = ImmutableSet.copyOf(columnList);
}
// use a dense table if more than half of the cells have values
// TODO(gak): tune this condition based on empirical evidence
return (cellSet.size() > ((rowSpace.size() * columnSpace.size()) / 2 )) ?
new DenseImmutableTable<R, C, V>(cellSet, rowSpace, columnSpace) :
new SparseImmutableTable<R, C, V>(cellSet, rowSpace, columnSpace);
}
/**
* A {@code RegularImmutableTable} optimized for sparse data.
*/
@Immutable
@VisibleForTesting
static final class SparseImmutableTable<R, C, V>
extends RegularImmutableTable<R, C, V> {
private final ImmutableMap<R, Map<C, V>> rowMap;
private final ImmutableMap<C, Map<R, V>> columnMap;
/**
* Creates a {@link Map} over the key space with
* {@link ImmutableMap.Builder}s ready for values.
*/
private static final <A, B, V> Map<A, ImmutableMap.Builder<B, V>>
makeIndexBuilder(ImmutableSet<A> keySpace) {
Map<A, ImmutableMap.Builder<B, V>> indexBuilder = Maps.newLinkedHashMap();
for (A key : keySpace) {
indexBuilder.put(key, ImmutableMap.<B, V>builder());
}
return indexBuilder;
}
/**
* Builds the value maps of the index and creates an immutable copy of the
* map.
*/
private static final <A, B, V> ImmutableMap<A, Map<B, V>> buildIndex(
Map<A, ImmutableMap.Builder<B, V>> indexBuilder) {
return ImmutableMap.copyOf(Maps.transformValues(indexBuilder,
new Function<ImmutableMap.Builder<B, V>, Map<B, V>>() {
@Override public Map<B, V> apply(ImmutableMap.Builder<B, V> from) {
return from.build();
}
}));
}
SparseImmutableTable(ImmutableSet<Cell<R, C, V>> cellSet,
ImmutableSet<R> rowSpace, ImmutableSet<C> columnSpace) {
super(cellSet);
Map<R, ImmutableMap.Builder<C, V>> rowIndexBuilder
= makeIndexBuilder(rowSpace);
Map<C, ImmutableMap.Builder<R, V>> columnIndexBuilder
= makeIndexBuilder(columnSpace);
for (Cell<R, C, V> cell : cellSet) {
R rowKey = cell.getRowKey();
C columnKey = cell.getColumnKey();
V value = cell.getValue();
rowIndexBuilder.get(rowKey).put(columnKey, value);
columnIndexBuilder.get(columnKey).put(rowKey, value);
}
this.rowMap = buildIndex(rowIndexBuilder);
this.columnMap = buildIndex(columnIndexBuilder);
}
@Override public ImmutableMap<R, V> column(C columnKey) {
checkNotNull(columnKey);
// value maps are guaranteed to be immutable maps
return Objects.firstNonNull((ImmutableMap<R, V>) columnMap.get(columnKey),
ImmutableMap.<R, V>of());
}
@Override public ImmutableSet<C> columnKeySet() {
return columnMap.keySet();
}
@Override public ImmutableMap<C, Map<R, V>> columnMap() {
return columnMap;
}
@Override public ImmutableMap<C, V> row(R rowKey) {
checkNotNull(rowKey);
// value maps are guaranteed to be immutable maps
return Objects.firstNonNull((ImmutableMap<C, V>) rowMap.get(rowKey),
ImmutableMap.<C, V>of());
}
@Override public ImmutableSet<R> rowKeySet() {
return rowMap.keySet();
}
@Override public ImmutableMap<R, Map<C, V>> rowMap() {
return rowMap;
}
@Override public boolean contains(@Nullable Object rowKey,
@Nullable Object columnKey) {
Map<C, V> row = rowMap.get(rowKey);
return (row != null) && row.containsKey(columnKey);
}
@Override public boolean containsColumn(@Nullable Object columnKey) {
return columnMap.containsKey(columnKey);
}
@Override public boolean containsRow(@Nullable Object rowKey) {
return rowMap.containsKey(rowKey);
}
@Override public V get(@Nullable Object rowKey,
@Nullable Object columnKey) {
Map<C, V> row = rowMap.get(rowKey);
return (row == null) ? null : row.get(columnKey);
}
}
/**
* An immutable map implementation backed by an indexed nullable array, used in
* DenseImmutableTable.
*/
private abstract static class ImmutableArrayMap<K, V> extends ImmutableMap<K, V> {
private final int size;
ImmutableArrayMap(int size) {
this.size = size;
}
abstract ImmutableMap<K, Integer> keyToIndex();
// True if getValue never returns null.
private boolean isFull() {
return size == keyToIndex().size();
}
K getKey(int index) {
return keyToIndex().keySet().asList().get(index);
}
@Nullable abstract V getValue(int keyIndex);
@Override
ImmutableSet<K> createKeySet() {
return isFull() ? keyToIndex().keySet() : super.createKeySet();
}
@Override
public int size() {
return size;
}
@Override
public V get(@Nullable Object key) {
Integer keyIndex = keyToIndex().get(key);
return (keyIndex == null) ? null : getValue(keyIndex);
}
@Override
ImmutableSet<Entry<K, V>> createEntrySet() {
if (isFull()) {
return new ImmutableMapEntrySet<K, V>() {
@Override ImmutableMap<K, V> map() {
return ImmutableArrayMap.this;
}
@Override
public UnmodifiableIterator<Entry<K, V>> iterator() {
return new AbstractIndexedListIterator<Entry<K, V>>(size()) {
@Override
protected Entry<K, V> get(int index) {
return Maps.immutableEntry(getKey(index), getValue(index));
}
};
}
};
} else {
return new ImmutableMapEntrySet<K, V>() {
@Override ImmutableMap<K, V> map() {
return ImmutableArrayMap.this;
}
@Override
public UnmodifiableIterator<Entry<K, V>> iterator() {
return new AbstractIterator<Entry<K, V>>() {
private int index = -1;
private final int maxIndex = keyToIndex().size();
@Override
protected Entry<K, V> computeNext() {
for (index++; index < maxIndex; index++) {
V value = getValue(index);
if (value != null) {
return Maps.immutableEntry(getKey(index), value);
}
}
return endOfData();
}
};
}
};
}
}
}
/**
* A {@code RegularImmutableTable} optimized for dense data.
*/
@Immutable @VisibleForTesting
static final class DenseImmutableTable<R, C, V>
extends RegularImmutableTable<R, C, V> {
private final ImmutableMap<R, Integer> rowKeyToIndex;
private final ImmutableMap<C, Integer> columnKeyToIndex;
private final ImmutableMap<R, Map<C, V>> rowMap;
private final ImmutableMap<C, Map<R, V>> columnMap;
private final int[] rowCounts;
private final int[] columnCounts;
private final V[][] values;
private static <E> ImmutableMap<E, Integer> makeIndex(
ImmutableSet<E> set) {
ImmutableMap.Builder<E, Integer> indexBuilder =
ImmutableMap.builder();
int i = 0;
for (E key : set) {
indexBuilder.put(key, i);
i++;
}
return indexBuilder.build();
}
DenseImmutableTable(ImmutableSet<Cell<R, C, V>> cellSet,
ImmutableSet<R> rowSpace, ImmutableSet<C> columnSpace) {
super(cellSet);
@SuppressWarnings("unchecked")
V[][] array = (V[][]) new Object[rowSpace.size()][columnSpace.size()];
this.values = array;
this.rowKeyToIndex = makeIndex(rowSpace);
this.columnKeyToIndex = makeIndex(columnSpace);
rowCounts = new int[rowKeyToIndex.size()];
columnCounts = new int[columnKeyToIndex.size()];
for (Cell<R, C, V> cell : cellSet) {
R rowKey = cell.getRowKey();
C columnKey = cell.getColumnKey();
int rowIndex = rowKeyToIndex.get(rowKey);
int columnIndex = columnKeyToIndex.get(columnKey);
V existingValue = values[rowIndex][columnIndex];
checkArgument(existingValue == null, "duplicate key: (%s, %s)", rowKey,
columnKey);
values[rowIndex][columnIndex] = cell.getValue();
rowCounts[rowIndex]++;
columnCounts[columnIndex]++;
}
this.rowMap = new RowMap();
this.columnMap = new ColumnMap();
}
private final class Row extends ImmutableArrayMap<C, V> {
private final int rowIndex;
Row(int rowIndex) {
super(rowCounts[rowIndex]);
this.rowIndex = rowIndex;
}
@Override
ImmutableMap<C, Integer> keyToIndex() {
return columnKeyToIndex;
}
@Override
V getValue(int keyIndex) {
return values[rowIndex][keyIndex];
}
@Override
boolean isPartialView() {
return true;
}
}
private final class Column extends ImmutableArrayMap<R, V> {
private final int columnIndex;
Column(int columnIndex) {
super(columnCounts[columnIndex]);
this.columnIndex = columnIndex;
}
@Override
ImmutableMap<R, Integer> keyToIndex() {
return rowKeyToIndex;
}
@Override
V getValue(int keyIndex) {
return values[keyIndex][columnIndex];
}
@Override
boolean isPartialView() {
return true;
}
}
private final class RowMap extends ImmutableArrayMap<R, Map<C, V>> {
private RowMap() {
super(rowCounts.length);
}
@Override
ImmutableMap<R, Integer> keyToIndex() {
return rowKeyToIndex;
}
@Override
Map<C, V> getValue(int keyIndex) {
return new Row(keyIndex);
}
@Override
boolean isPartialView() {
return false;
}
}
private final class ColumnMap extends ImmutableArrayMap<C, Map<R, V>> {
private ColumnMap() {
super(columnCounts.length);
}
@Override
ImmutableMap<C, Integer> keyToIndex() {
return columnKeyToIndex;
}
@Override
Map<R, V> getValue(int keyIndex) {
return new Column(keyIndex);
}
@Override
boolean isPartialView() {
return false;
}
}
@Override public ImmutableMap<R, V> column(C columnKey) {
Integer columnIndex = columnKeyToIndex.get(checkNotNull(columnKey));
if (columnIndex == null) {
return ImmutableMap.of();
} else {
return new Column(columnIndex);
}
}
@Override public ImmutableSet<C> columnKeySet() {
return columnKeyToIndex.keySet();
}
@Override public ImmutableMap<C, Map<R, V>> columnMap() {
return columnMap;
}
@Override public boolean contains(@Nullable Object rowKey,
@Nullable Object columnKey) {
return (get(rowKey, columnKey) != null);
}
@Override public boolean containsColumn(@Nullable Object columnKey) {
return columnKeyToIndex.containsKey(columnKey);
}
@Override public boolean containsRow(@Nullable Object rowKey) {
return rowKeyToIndex.containsKey(rowKey);
}
@Override public V get(@Nullable Object rowKey,
@Nullable Object columnKey) {
Integer rowIndex = rowKeyToIndex.get(rowKey);
Integer columnIndex = columnKeyToIndex.get(columnKey);
return ((rowIndex == null) || (columnIndex == null)) ? null
: values[rowIndex][columnIndex];
}
@Override public ImmutableMap<C, V> row(R rowKey) {
checkNotNull(rowKey);
Integer rowIndex = rowKeyToIndex.get(rowKey);
if (rowIndex == null) {
return ImmutableMap.of();
} else {
return new Row(rowIndex);
}
}
@Override public ImmutableSet<R> rowKeySet() {
return rowKeyToIndex.keySet();
}
@Override
public ImmutableMap<R, Map<C, V>> rowMap() {
return rowMap;
}
}
}