All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.Tables Maven / Gradle / Ivy

Go to download

Guava is a suite of core and expanded libraries that include utility classes, google's collections, io classes, and much much more.

There is a newer version: 33.1.0-jre
Show newest version
/*
 * Copyright (C) 2008 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.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.collect.Table.Cell;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Spliterator;
import java.util.function.BinaryOperator;
import java.util.stream.Collector;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Provides static methods that involve a {@code Table}.
 *
 * 

See the Guava User Guide article on {@code Tables}. * * @author Jared Levy * @author Louis Wasserman * @since 7.0 */ @GwtCompatible public final class Tables { private Tables() {} /** * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the * specified supplier, whose cells are generated by applying the provided mapping functions to the * input elements. Cells are inserted into the generated {@code Table} in encounter order. * *

If multiple input elements map to the same row and column, an {@code IllegalStateException} * is thrown when the collection operation is performed. * * @since 21.0 */ @Beta public static > Collector toTable( java.util.function.Function rowFunction, java.util.function.Function columnFunction, java.util.function.Function valueFunction, java.util.function.Supplier tableSupplier) { return toTable( rowFunction, columnFunction, valueFunction, (v1, v2) -> { throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); }, tableSupplier); } /** * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the * specified supplier, whose cells are generated by applying the provided mapping functions to the * input elements. Cells are inserted into the generated {@code Table} in encounter order. * *

If multiple input elements map to the same row and column, the specified merging function is * used to combine the values. Like {@link * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls * returned from {@code mergeFunction} as removals of that row/column pair. * * @since 21.0 */ public static > Collector toTable( java.util.function.Function rowFunction, java.util.function.Function columnFunction, java.util.function.Function valueFunction, BinaryOperator mergeFunction, java.util.function.Supplier tableSupplier) { checkNotNull(rowFunction); checkNotNull(columnFunction); checkNotNull(valueFunction); checkNotNull(mergeFunction); checkNotNull(tableSupplier); return Collector.of( tableSupplier, (table, input) -> merge( table, rowFunction.apply(input), columnFunction.apply(input), valueFunction.apply(input), mergeFunction), (table1, table2) -> { for (Table.Cell cell2 : table2.cellSet()) { merge(table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); } return table1; }); } private static void merge( Table table, R row, C column, V value, BinaryOperator mergeFunction) { checkNotNull(value); V oldValue = table.get(row, column); if (oldValue == null) { table.put(row, column, value); } else { V newValue = mergeFunction.apply(oldValue, value); if (newValue == null) { table.remove(row, column); } else { table.put(row, column, newValue); } } } /** * Returns an immutable cell with the specified row key, column key, and value. * *

The returned cell is serializable. * * @param rowKey the row key to be associated with the returned cell * @param columnKey the column key to be associated with the returned cell * @param value the value to be associated with the returned cell */ public static Cell immutableCell( @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { return new ImmutableCell<>(rowKey, columnKey, value); } static final class ImmutableCell extends AbstractCell implements Serializable { private final @Nullable R rowKey; private final @Nullable C columnKey; private final @Nullable V value; ImmutableCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { this.rowKey = rowKey; this.columnKey = columnKey; this.value = value; } @Override public R getRowKey() { return rowKey; } @Override public C getColumnKey() { return columnKey; } @Override public V getValue() { return value; } private static final long serialVersionUID = 0; } abstract static class AbstractCell implements Cell { // needed for serialization AbstractCell() {} @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Cell) { Cell other = (Cell) obj; return Objects.equal(getRowKey(), other.getRowKey()) && Objects.equal(getColumnKey(), other.getColumnKey()) && Objects.equal(getValue(), other.getValue()); } return false; } @Override public int hashCode() { return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); } @Override public String toString() { return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); } } /** * Creates a transposed view of a given table that flips its row and column keys. In other words, * calling {@code get(columnKey, rowKey)} on the generated table always returns the same value as * calling {@code get(rowKey, columnKey)} on the original table. Updating the original table * changes the contents of the transposed table and vice versa. * *

The returned table supports update operations as long as the input table supports the * analogous operation with swapped rows and columns. For example, in a {@link HashBasedTable} * instance, {@code rowKeySet().iterator()} supports {@code remove()} but {@code * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other * way around. */ public static Table transpose(Table table) { return (table instanceof TransposeTable) ? ((TransposeTable) table).original : new TransposeTable(table); } private static class TransposeTable extends AbstractTable { final Table original; TransposeTable(Table original) { this.original = checkNotNull(original); } @Override public void clear() { original.clear(); } @Override public Map column(R columnKey) { return original.row(columnKey); } @Override public Set columnKeySet() { return original.rowKeySet(); } @Override public Map> columnMap() { return original.rowMap(); } @Override public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return original.contains(columnKey, rowKey); } @Override public boolean containsColumn(@Nullable Object columnKey) { return original.containsRow(columnKey); } @Override public boolean containsRow(@Nullable Object rowKey) { return original.containsColumn(rowKey); } @Override public boolean containsValue(@Nullable Object value) { return original.containsValue(value); } @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { return original.get(columnKey, rowKey); } @Override public V put(C rowKey, R columnKey, V value) { return original.put(columnKey, rowKey, value); } @Override public void putAll(Table table) { original.putAll(transpose(table)); } @Override public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { return original.remove(columnKey, rowKey); } @Override public Map row(C rowKey) { return original.column(rowKey); } @Override public Set rowKeySet() { return original.columnKeySet(); } @Override public Map> rowMap() { return original.columnMap(); } @Override public int size() { return original.size(); } @Override public Collection values() { return original.values(); } // Will cast TRANSPOSE_CELL to a type that always succeeds private static final Function, Cell> TRANSPOSE_CELL = new Function, Cell>() { @Override public Cell apply(Cell cell) { return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); } }; @SuppressWarnings("unchecked") @Override Iterator> cellIterator() { return Iterators.transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); } @SuppressWarnings("unchecked") @Override Spliterator> cellSpliterator() { return CollectSpliterators.map(original.cellSet().spliterator(), (Function) TRANSPOSE_CELL); } } /** * Creates a table that uses the specified backing map and factory. It can generate a table based * on arbitrary {@link Map} classes. * *

The {@code factory}-generated and {@code backingMap} classes determine the table iteration * order. However, the table's {@code row()} method returns instances of a different class than * {@code factory.get()} does. * *

Call this method only when the simpler factory methods in classes like {@link * HashBasedTable} and {@link TreeBasedTable} won't suffice. * *

The views returned by the {@code Table} methods {@link Table#column}, {@link * Table#columnKeySet}, and {@link Table#columnMap} have iterators that don't support {@code * remove()}. Otherwise, all optional operations are supported. Null row keys, columns keys, and * values are not supported. * *

Lookups by row key are often faster than lookups by column key, because the data is stored * in a {@code Map>}. A method call like {@code column(columnKey).get(rowKey)} still * runs quickly, since the row key is provided. However, {@code column(columnKey).size()} takes * longer, since an iteration across all row keys occurs. * *

Note that this implementation is not synchronized. If multiple threads access this table * concurrently and one of the threads modifies the table, it must be synchronized externally. * *

The table is serializable if {@code backingMap}, {@code factory}, the maps generated by * {@code factory}, and the table contents are all serializable. * *

Note: the table assumes complete ownership over of {@code backingMap} and the maps returned * by {@code factory}. Those objects should not be manually updated and they should not use soft, * weak, or phantom references. * * @param backingMap place to store the mapping from each row key to its corresponding column key * / value map * @param factory supplier of new, empty maps that will each hold all column key / value mappings * for a given row key * @throws IllegalArgumentException if {@code backingMap} is not empty * @since 10.0 */ @Beta public static Table newCustomTable( Map> backingMap, Supplier> factory) { checkArgument(backingMap.isEmpty()); checkNotNull(factory); // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? return new StandardTable<>(backingMap, factory); } /** * Returns a view of a table where each value is transformed by a function. All other properties * of the table, such as iteration order, are left intact. * *

Changes in the underlying table are reflected in this view. Conversely, this view supports * removal operations, and these are reflected in the underlying table. * *

It's acceptable for the underlying table to contain null keys, and even null values provided * that the function is capable of accepting null input. The transformed table might contain null * values, if the function sometimes gives a null result. * *

The returned table is not thread-safe or serializable, even if the underlying table is. * *

The function is applied lazily, invoked when needed. This is necessary for the returned * table to be a view, but it means that the function will be applied many times for bulk * operations like {@link Table#containsValue} and {@code Table.toString()}. For this to perform * well, {@code function} should be fast. To avoid lazy evaluation when the returned table doesn't * need to be a view, copy the returned table into a new table of your choosing. * * @since 10.0 */ @Beta public static Table transformValues( Table fromTable, Function function) { return new TransformedTable<>(fromTable, function); } private static class TransformedTable extends AbstractTable { final Table fromTable; final Function function; TransformedTable(Table fromTable, Function function) { this.fromTable = checkNotNull(fromTable); this.function = checkNotNull(function); } @Override public boolean contains(Object rowKey, Object columnKey) { return fromTable.contains(rowKey, columnKey); } @Override public V2 get(Object rowKey, Object columnKey) { // The function is passed a null input only when the table contains a null // value. return contains(rowKey, columnKey) ? function.apply(fromTable.get(rowKey, columnKey)) : null; } @Override public int size() { return fromTable.size(); } @Override public void clear() { fromTable.clear(); } @Override public V2 put(R rowKey, C columnKey, V2 value) { throw new UnsupportedOperationException(); } @Override public void putAll(Table table) { throw new UnsupportedOperationException(); } @Override public V2 remove(Object rowKey, Object columnKey) { return contains(rowKey, columnKey) ? function.apply(fromTable.remove(rowKey, columnKey)) : null; } @Override public Map row(R rowKey) { return Maps.transformValues(fromTable.row(rowKey), function); } @Override public Map column(C columnKey) { return Maps.transformValues(fromTable.column(columnKey), function); } Function, Cell> cellFunction() { return new Function, Cell>() { @Override public Cell apply(Cell cell) { return immutableCell( cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); } }; } @Override Iterator> cellIterator() { return Iterators.transform(fromTable.cellSet().iterator(), cellFunction()); } @Override Spliterator> cellSpliterator() { return CollectSpliterators.map(fromTable.cellSet().spliterator(), cellFunction()); } @Override public Set rowKeySet() { return fromTable.rowKeySet(); } @Override public Set columnKeySet() { return fromTable.columnKeySet(); } @Override Collection createValues() { return Collections2.transform(fromTable.values(), function); } @Override public Map> rowMap() { Function, Map> rowFunction = new Function, Map>() { @Override public Map apply(Map row) { return Maps.transformValues(row, function); } }; return Maps.transformValues(fromTable.rowMap(), rowFunction); } @Override public Map> columnMap() { Function, Map> columnFunction = new Function, Map>() { @Override public Map apply(Map column) { return Maps.transformValues(column, function); } }; return Maps.transformValues(fromTable.columnMap(), columnFunction); } } /** * Returns an unmodifiable view of the specified table. This method allows modules to provide * users with "read-only" access to internal tables. Query operations on the returned table "read * through" to the specified table, and attempts to modify the returned table, whether direct or * via its collection views, result in an {@code UnsupportedOperationException}. * *

The returned table will be serializable if the specified table is serializable. * *

Consider using an {@link ImmutableTable}, which is guaranteed never to change. * * @since 11.0 */ public static Table unmodifiableTable( Table table) { return new UnmodifiableTable<>(table); } private static class UnmodifiableTable extends ForwardingTable implements Serializable { final Table delegate; UnmodifiableTable(Table delegate) { this.delegate = checkNotNull(delegate); } @SuppressWarnings("unchecked") // safe, covariant cast @Override protected Table delegate() { return (Table) delegate; } @Override public Set> cellSet() { return Collections.unmodifiableSet(super.cellSet()); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Map column(@Nullable C columnKey) { return Collections.unmodifiableMap(super.column(columnKey)); } @Override public Set columnKeySet() { return Collections.unmodifiableSet(super.columnKeySet()); } @Override public Map> columnMap() { Function, Map> wrapper = unmodifiableWrapper(); return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); } @Override public V put(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { throw new UnsupportedOperationException(); } @Override public void putAll(Table table) { throw new UnsupportedOperationException(); } @Override public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { throw new UnsupportedOperationException(); } @Override public Map row(@Nullable R rowKey) { return Collections.unmodifiableMap(super.row(rowKey)); } @Override public Set rowKeySet() { return Collections.unmodifiableSet(super.rowKeySet()); } @Override public Map> rowMap() { Function, Map> wrapper = unmodifiableWrapper(); return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); } @Override public Collection values() { return Collections.unmodifiableCollection(super.values()); } private static final long serialVersionUID = 0; } /** * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to * provide users with "read-only" access to internal tables. Query operations on the returned * table "read through" to the specified table, and attempts to modify the returned table, whether * direct or via its collection views, result in an {@code UnsupportedOperationException}. * *

The returned table will be serializable if the specified table is serializable. * * @param table the row-sorted table for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified table * @since 11.0 */ @Beta public static RowSortedTable unmodifiableRowSortedTable( RowSortedTable table) { /* * It's not ? extends R, because it's technically not covariant in R. Specifically, * table.rowMap().comparator() could return a comparator that only works for the ? extends R. * Collections.unmodifiableSortedMap makes the same distinction. */ return new UnmodifiableRowSortedMap<>(table); } static final class UnmodifiableRowSortedMap extends UnmodifiableTable implements RowSortedTable { public UnmodifiableRowSortedMap(RowSortedTable delegate) { super(delegate); } @Override protected RowSortedTable delegate() { return (RowSortedTable) super.delegate(); } @Override public SortedMap> rowMap() { Function, Map> wrapper = unmodifiableWrapper(); return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); } @Override public SortedSet rowKeySet() { return Collections.unmodifiableSortedSet(delegate().rowKeySet()); } private static final long serialVersionUID = 0; } @SuppressWarnings("unchecked") private static Function, Map> unmodifiableWrapper() { return (Function) UNMODIFIABLE_WRAPPER; } private static final Function, ? extends Map> UNMODIFIABLE_WRAPPER = new Function, Map>() { @Override public Map apply(Map input) { return Collections.unmodifiableMap(input); } }; /** * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee * serial access, it is critical that all access to the backing table is accomplished * through the returned table. * *

It is imperative that the user manually synchronize on the returned table when accessing any * of its collection views: * *

{@code
   * Table table = Tables.synchronizedTable(HashBasedTable.create());
   * ...
   * Map row = table.row(rowKey);  // Needn't be in synchronized block
   * ...
   * synchronized (table) {  // Synchronizing on table, not row!
   *   Iterator> i = row.entrySet().iterator(); // Must be in synchronized block
   *   while (i.hasNext()) {
   *     foo(i.next());
   *   }
   * }
   * }
* *

Failure to follow this advice may result in non-deterministic behavior. * *

The returned table will be serializable if the specified table is serializable. * * @param table the table to be wrapped in a synchronized view * @return a synchronized view of the specified table * @since 22.0 */ public static Table synchronizedTable(Table table) { return Synchronized.table(table, null); } static boolean equalsImpl(Table table, @Nullable Object obj) { if (obj == table) { return true; } else if (obj instanceof Table) { Table that = (Table) obj; return table.cellSet().equals(that.cellSet()); } else { return false; } } }