blob: 99ab810683d6323b013970930904bd792b9f15ca [file] [log] [blame]
/*
* 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.checkNotNull;
import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.Tables.AbstractCell;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import org.checkerframework.checker.nullness.qual.Nullable;
/** Collectors utilities for {@code common.collect.Table} internals. */
@GwtCompatible
@ElementTypesAreNonnullByDefault
final class TableCollectors {
static <T extends @Nullable Object, R, C, V>
Collector<T, ?, ImmutableTable<R, C, V>> toImmutableTable(
Function<? super T, ? extends R> rowFunction,
Function<? super T, ? extends C> columnFunction,
Function<? super T, ? extends V> valueFunction) {
checkNotNull(rowFunction, "rowFunction");
checkNotNull(columnFunction, "columnFunction");
checkNotNull(valueFunction, "valueFunction");
return Collector.of(
(Supplier<ImmutableTable.Builder<R, C, V>>) ImmutableTable.Builder::new,
(builder, t) ->
builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)),
ImmutableTable.Builder::combine,
ImmutableTable.Builder::build);
}
static <T extends @Nullable Object, R, C, V>
Collector<T, ?, ImmutableTable<R, C, V>> toImmutableTable(
Function<? super T, ? extends R> rowFunction,
Function<? super T, ? extends C> columnFunction,
Function<? super T, ? extends V> valueFunction,
BinaryOperator<V> mergeFunction) {
checkNotNull(rowFunction, "rowFunction");
checkNotNull(columnFunction, "columnFunction");
checkNotNull(valueFunction, "valueFunction");
checkNotNull(mergeFunction, "mergeFunction");
/*
* No mutable Table exactly matches the insertion order behavior of ImmutableTable.Builder, but
* the Builder can't efficiently support merging of duplicate values. Getting around this
* requires some work.
*/
return Collector.of(
() -> new ImmutableTableCollectorState<R, C, V>()
/* GWT isn't currently playing nicely with constructor references? */ ,
(state, input) ->
state.put(
rowFunction.apply(input),
columnFunction.apply(input),
valueFunction.apply(input),
mergeFunction),
(s1, s2) -> s1.combine(s2, mergeFunction),
state -> state.toTable());
}
static <
T extends @Nullable Object,
R extends @Nullable Object,
C extends @Nullable Object,
V extends @Nullable Object,
I extends Table<R, C, V>>
Collector<T, ?, I> toTable(
java.util.function.Function<? super T, ? extends R> rowFunction,
java.util.function.Function<? super T, ? extends C> columnFunction,
java.util.function.Function<? super T, ? extends V> valueFunction,
java.util.function.Supplier<I> tableSupplier) {
return toTable(
rowFunction,
columnFunction,
valueFunction,
(v1, v2) -> {
throw new IllegalStateException("Conflicting values " + v1 + " and " + v2);
},
tableSupplier);
}
static <
T extends @Nullable Object,
R extends @Nullable Object,
C extends @Nullable Object,
V extends @Nullable Object,
I extends Table<R, C, V>>
Collector<T, ?, I> toTable(
java.util.function.Function<? super T, ? extends R> rowFunction,
java.util.function.Function<? super T, ? extends C> columnFunction,
java.util.function.Function<? super T, ? extends V> valueFunction,
BinaryOperator<V> mergeFunction,
java.util.function.Supplier<I> tableSupplier) {
checkNotNull(rowFunction);
checkNotNull(columnFunction);
checkNotNull(valueFunction);
checkNotNull(mergeFunction);
checkNotNull(tableSupplier);
return Collector.of(
tableSupplier,
(table, input) ->
mergeTables(
table,
rowFunction.apply(input),
columnFunction.apply(input),
valueFunction.apply(input),
mergeFunction),
(table1, table2) -> {
for (Table.Cell<R, C, V> cell2 : table2.cellSet()) {
mergeTables(
table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction);
}
return table1;
});
}
private static final class ImmutableTableCollectorState<R, C, V> {
final List<MutableCell<R, C, V>> insertionOrder = new ArrayList<>();
final Table<R, C, MutableCell<R, C, V>> table = HashBasedTable.create();
void put(R row, C column, V value, BinaryOperator<V> merger) {
MutableCell<R, C, V> oldCell = table.get(row, column);
if (oldCell == null) {
MutableCell<R, C, V> cell = new MutableCell<>(row, column, value);
insertionOrder.add(cell);
table.put(row, column, cell);
} else {
oldCell.merge(value, merger);
}
}
ImmutableTableCollectorState<R, C, V> combine(
ImmutableTableCollectorState<R, C, V> other, BinaryOperator<V> merger) {
for (MutableCell<R, C, V> cell : other.insertionOrder) {
put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger);
}
return this;
}
ImmutableTable<R, C, V> toTable() {
return ImmutableTable.copyOf(insertionOrder);
}
}
private static final class MutableCell<R, C, V> extends AbstractCell<R, C, V> {
private final R row;
private final C column;
private V value;
MutableCell(R row, C column, V value) {
this.row = checkNotNull(row, "row");
this.column = checkNotNull(column, "column");
this.value = checkNotNull(value, "value");
}
@Override
public R getRowKey() {
return row;
}
@Override
public C getColumnKey() {
return column;
}
@Override
public V getValue() {
return value;
}
void merge(V value, BinaryOperator<V> mergeFunction) {
checkNotNull(value, "value");
this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply");
}
}
private static <
R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object>
void mergeTables(
Table<R, C, V> table,
@ParametricNullness R row,
@ParametricNullness C column,
@ParametricNullness V value,
BinaryOperator<V> 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);
}
}
}
private TableCollectors() {}
}