/* * Copyright (C) 2009 Google Inc. * * 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 gak@google.com (Gregory Kick) */ @GwtCompatible abstract class RegularImmutableTable extends ImmutableTable { private final ImmutableSet> cellSet; private RegularImmutableTable(ImmutableSet> cellSet) { this.cellSet = cellSet; } private static final Function, Object> GET_VALUE_FUNCTION = new Function, Object>() { @Override public Object apply(Cell from) { return from.getValue(); } }; @SuppressWarnings("unchecked") private Function, V> getValueFunction() { return (Function) GET_VALUE_FUNCTION; } @Nullable private transient volatile ImmutableList valueList; @Override public final ImmutableCollection values() { ImmutableList 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> cellSet() { return cellSet; } static final RegularImmutableTable forCells( List> cells, @Nullable final Comparator rowComparator, @Nullable final Comparator 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> comparator = new Comparator>() { @Override public int compare(Cell cell1, Cell 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 RegularImmutableTable forCells( Iterable> cells) { return forCellsInternal(cells, null, null); } /** * A factory that chooses the most space-efficient representation of the * table. */ private static final RegularImmutableTable forCellsInternal(Iterable> cells, @Nullable Comparator rowComparator, @Nullable Comparator columnComparator) { ImmutableSet.Builder> cellSetBuilder = ImmutableSet.builder(); ImmutableSet.Builder rowSpaceBuilder = ImmutableSet.builder(); ImmutableSet.Builder columnSpaceBuilder = ImmutableSet.builder(); for (Cell cell : cells) { cellSetBuilder.add(cell); rowSpaceBuilder.add(cell.getRowKey()); columnSpaceBuilder.add(cell.getColumnKey()); } ImmutableSet> cellSet = cellSetBuilder.build(); ImmutableSet rowSpace = rowSpaceBuilder.build(); if (rowComparator != null) { List rowList = Lists.newArrayList(rowSpace); Collections.sort(rowList, rowComparator); rowSpace = ImmutableSet.copyOf(rowList); } ImmutableSet columnSpace = columnSpaceBuilder.build(); if (columnComparator != null) { List 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(cellSet, rowSpace, columnSpace) : new SparseImmutableTable(cellSet, rowSpace, columnSpace); } /** * A {@code RegularImmutableTable} optimized for sparse data. */ @Immutable @VisibleForTesting static final class SparseImmutableTable extends RegularImmutableTable { private final ImmutableMap> rowMap; private final ImmutableMap> columnMap; /** * Creates a {@link Map} over the key space with * {@link ImmutableMap.Builder}s ready for values. */ private static final Map> makeIndexBuilder(ImmutableSet keySpace) { Map> indexBuilder = Maps.newLinkedHashMap(); for (A key : keySpace) { indexBuilder.put(key, ImmutableMap.builder()); } return indexBuilder; } /** * Builds the value maps of the index and creates an immutable copy of the * map. */ private static final ImmutableMap> buildIndex( Map> indexBuilder) { return ImmutableMap.copyOf(Maps.transformValues(indexBuilder, new Function, Map>() { @Override public Map apply(ImmutableMap.Builder from) { return from.build(); } })); } SparseImmutableTable(ImmutableSet> cellSet, ImmutableSet rowSpace, ImmutableSet columnSpace) { super(cellSet); Map> rowIndexBuilder = makeIndexBuilder(rowSpace); Map> columnIndexBuilder = makeIndexBuilder(columnSpace); for (Cell 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 column(C columnKey) { checkNotNull(columnKey); // value maps are guaranteed to be immutable maps return Objects.firstNonNull((ImmutableMap) columnMap.get(columnKey), ImmutableMap.of()); } @Override public ImmutableSet columnKeySet() { return columnMap.keySet(); } @Override public ImmutableMap> columnMap() { return columnMap; } @Override public ImmutableMap row(R rowKey) { checkNotNull(rowKey); // value maps are guaranteed to be immutable maps return Objects.firstNonNull((ImmutableMap) rowMap.get(rowKey), ImmutableMap.of()); } @Override public ImmutableSet rowKeySet() { return rowMap.keySet(); } @Override public ImmutableMap> rowMap() { return rowMap; } @Override public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { Map 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 row = rowMap.get(rowKey); return (row == null) ? null : row.get(columnKey); } } /** * A {@code RegularImmutableTable} optimized for dense data. */ @Immutable @VisibleForTesting static final class DenseImmutableTable extends RegularImmutableTable { private final ImmutableBiMap rowKeyToIndex; private final ImmutableBiMap columnKeyToIndex; private final V[][] values; private static ImmutableBiMap makeIndex( ImmutableSet set) { ImmutableBiMap.Builder indexBuilder = ImmutableBiMap.builder(); int i = 0; for (E key : set) { indexBuilder.put(key, i); i++; } return indexBuilder.build(); } DenseImmutableTable(ImmutableSet> cellSet, ImmutableSet rowSpace, ImmutableSet 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); for (Cell 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(); } } @Override public ImmutableMap column(C columnKey) { checkNotNull(columnKey); Integer columnIndexInteger = columnKeyToIndex.get(columnKey); if (columnIndexInteger == null) { return ImmutableMap.of(); } else { // unbox only once int columnIndex = columnIndexInteger; ImmutableMap.Builder columnBuilder = ImmutableMap.builder(); for (int i = 0; i < values.length; i++) { V value = values[i][columnIndex]; if (value != null) { columnBuilder.put(rowKeyToIndex.inverse().get(i), value); } } return columnBuilder.build(); } } @Override public ImmutableSet columnKeySet() { return columnKeyToIndex.keySet(); } private transient volatile ImmutableMap> columnMap; private ImmutableMap> makeColumnMap() { ImmutableMap.Builder> columnMapBuilder = ImmutableMap.builder(); for (int c = 0; c < columnKeyToIndex.size(); c++) { ImmutableMap.Builder rowMapBuilder = ImmutableMap.builder(); for (int r = 0; r < rowKeyToIndex.size(); r++) { V value = values[r][c]; if (value != null) { rowMapBuilder.put(rowKeyToIndex.inverse().get(r), value); } } columnMapBuilder.put(columnKeyToIndex.inverse().get(c), rowMapBuilder.build()); } return columnMapBuilder.build(); } @Override public ImmutableMap> columnMap() { ImmutableMap> result = columnMap; if (result == null) { columnMap = result = makeColumnMap(); } return result; } @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 row(R rowKey) { checkNotNull(rowKey); Integer rowIndex = rowKeyToIndex.get(rowKey); if (rowIndex == null) { return ImmutableMap.of(); } else { ImmutableMap.Builder rowBuilder = ImmutableMap.builder(); V[] row = values[rowIndex]; for (int r = 0; r < row.length; r++) { V value = row[r]; if (value != null) { rowBuilder.put(columnKeyToIndex.inverse().get(r), value); } } return rowBuilder.build(); } } @Override public ImmutableSet rowKeySet() { return rowKeyToIndex.keySet(); } private transient volatile ImmutableMap> rowMap; private ImmutableMap> makeRowMap() { ImmutableMap.Builder> rowMapBuilder = ImmutableMap.builder(); for (int r = 0; r < values.length; r++) { V[] row = values[r]; ImmutableMap.Builder columnMapBuilder = ImmutableMap.builder(); for (int c = 0; c < row.length; c++) { V value = row[c]; if (value != null) { columnMapBuilder.put(columnKeyToIndex.inverse().get(c), value); } } rowMapBuilder.put(rowKeyToIndex.inverse().get(r), columnMapBuilder.build()); } return rowMapBuilder.build(); } @Override public ImmutableMap> rowMap() { ImmutableMap> result = rowMap; if (result == null) { rowMap = result = makeRowMap(); } return result; } } }