1 /* 2 * Copyright (C) 2009 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.GwtCompatible; 22 import com.google.common.collect.Tables.AbstractCell; 23 import java.util.ArrayList; 24 import java.util.List; 25 import java.util.function.BinaryOperator; 26 import java.util.function.Function; 27 import java.util.function.Supplier; 28 import java.util.stream.Collector; 29 import org.checkerframework.checker.nullness.qual.Nullable; 30 31 /** Collectors utilities for {@code common.collect.Table} internals. */ 32 @GwtCompatible 33 @ElementTypesAreNonnullByDefault 34 final class TableCollectors { 35 36 static <T extends @Nullable Object, R, C, V> toImmutableTable( Function<? super T, ? extends R> rowFunction, Function<? super T, ? extends C> columnFunction, Function<? super T, ? extends V> valueFunction)37 Collector<T, ?, ImmutableTable<R, C, V>> toImmutableTable( 38 Function<? super T, ? extends R> rowFunction, 39 Function<? super T, ? extends C> columnFunction, 40 Function<? super T, ? extends V> valueFunction) { 41 checkNotNull(rowFunction, "rowFunction"); 42 checkNotNull(columnFunction, "columnFunction"); 43 checkNotNull(valueFunction, "valueFunction"); 44 return Collector.of( 45 (Supplier<ImmutableTable.Builder<R, C, V>>) ImmutableTable.Builder::new, 46 (builder, t) -> 47 builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)), 48 ImmutableTable.Builder::combine, 49 ImmutableTable.Builder::build); 50 } 51 52 static <T extends @Nullable Object, 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)53 Collector<T, ?, ImmutableTable<R, C, V>> toImmutableTable( 54 Function<? super T, ? extends R> rowFunction, 55 Function<? super T, ? extends C> columnFunction, 56 Function<? super T, ? extends V> valueFunction, 57 BinaryOperator<V> mergeFunction) { 58 59 checkNotNull(rowFunction, "rowFunction"); 60 checkNotNull(columnFunction, "columnFunction"); 61 checkNotNull(valueFunction, "valueFunction"); 62 checkNotNull(mergeFunction, "mergeFunction"); 63 64 /* 65 * No mutable Table exactly matches the insertion order behavior of ImmutableTable.Builder, but 66 * the Builder can't efficiently support merging of duplicate values. Getting around this 67 * requires some work. 68 */ 69 70 return Collector.of( 71 () -> new ImmutableTableCollectorState<R, C, V>() 72 /* GWT isn't currently playing nicely with constructor references? */ , 73 (state, input) -> 74 state.put( 75 rowFunction.apply(input), 76 columnFunction.apply(input), 77 valueFunction.apply(input), 78 mergeFunction), 79 (s1, s2) -> s1.combine(s2, mergeFunction), 80 state -> state.toTable()); 81 } 82 83 static < 84 T extends @Nullable Object, 85 R extends @Nullable Object, 86 C extends @Nullable Object, 87 V extends @Nullable Object, 88 I extends Table<R, C, V>> 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)89 Collector<T, ?, I> toTable( 90 java.util.function.Function<? super T, ? extends R> rowFunction, 91 java.util.function.Function<? super T, ? extends C> columnFunction, 92 java.util.function.Function<? super T, ? extends V> valueFunction, 93 java.util.function.Supplier<I> tableSupplier) { 94 return toTable( 95 rowFunction, 96 columnFunction, 97 valueFunction, 98 (v1, v2) -> { 99 throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); 100 }, 101 tableSupplier); 102 } 103 104 static < 105 T extends @Nullable Object, 106 R extends @Nullable Object, 107 C extends @Nullable Object, 108 V extends @Nullable Object, 109 I extends Table<R, C, V>> 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)110 Collector<T, ?, I> toTable( 111 java.util.function.Function<? super T, ? extends R> rowFunction, 112 java.util.function.Function<? super T, ? extends C> columnFunction, 113 java.util.function.Function<? super T, ? extends V> valueFunction, 114 BinaryOperator<V> mergeFunction, 115 java.util.function.Supplier<I> tableSupplier) { 116 checkNotNull(rowFunction); 117 checkNotNull(columnFunction); 118 checkNotNull(valueFunction); 119 checkNotNull(mergeFunction); 120 checkNotNull(tableSupplier); 121 return Collector.of( 122 tableSupplier, 123 (table, input) -> 124 mergeTables( 125 table, 126 rowFunction.apply(input), 127 columnFunction.apply(input), 128 valueFunction.apply(input), 129 mergeFunction), 130 (table1, table2) -> { 131 for (Table.Cell<R, C, V> cell2 : table2.cellSet()) { 132 mergeTables( 133 table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); 134 } 135 return table1; 136 }); 137 } 138 139 private static final class ImmutableTableCollectorState<R, C, V> { 140 final List<MutableCell<R, C, V>> insertionOrder = new ArrayList<>(); 141 final Table<R, C, MutableCell<R, C, V>> table = HashBasedTable.create(); 142 143 void put(R row, C column, V value, BinaryOperator<V> merger) { 144 MutableCell<R, C, V> oldCell = table.get(row, column); 145 if (oldCell == null) { 146 MutableCell<R, C, V> cell = new MutableCell<>(row, column, value); 147 insertionOrder.add(cell); 148 table.put(row, column, cell); 149 } else { 150 oldCell.merge(value, merger); 151 } 152 } 153 154 ImmutableTableCollectorState<R, C, V> combine( 155 ImmutableTableCollectorState<R, C, V> other, BinaryOperator<V> merger) { 156 for (MutableCell<R, C, V> cell : other.insertionOrder) { 157 put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger); 158 } 159 return this; 160 } 161 162 ImmutableTable<R, C, V> toTable() { 163 return ImmutableTable.copyOf(insertionOrder); 164 } 165 } 166 167 private static final class MutableCell<R, C, V> extends AbstractCell<R, C, V> { 168 private final R row; 169 private final C column; 170 private V value; 171 172 MutableCell(R row, C column, V value) { 173 this.row = checkNotNull(row, "row"); 174 this.column = checkNotNull(column, "column"); 175 this.value = checkNotNull(value, "value"); 176 } 177 178 @Override 179 public R getRowKey() { 180 return row; 181 } 182 183 @Override 184 public C getColumnKey() { 185 return column; 186 } 187 188 @Override 189 public V getValue() { 190 return value; 191 } 192 193 void merge(V value, BinaryOperator<V> mergeFunction) { 194 checkNotNull(value, "value"); 195 this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply"); 196 } 197 } 198 199 private static < 200 R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 201 void mergeTables( 202 Table<R, C, V> table, 203 @ParametricNullness R row, 204 @ParametricNullness C column, 205 @ParametricNullness V value, 206 BinaryOperator<V> mergeFunction) { 207 checkNotNull(value); 208 V oldValue = table.get(row, column); 209 if (oldValue == null) { 210 table.put(row, column, value); 211 } else { 212 V newValue = mergeFunction.apply(oldValue, value); 213 if (newValue == null) { 214 table.remove(row, column); 215 } else { 216 table.put(row, column, newValue); 217 } 218 } 219 } 220 221 private TableCollectors() {} 222 } 223