001/* 002 * Copyright (C) 2008 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.collect; 018 019import static com.google.common.base.Preconditions.checkArgument; 020import static com.google.common.base.Preconditions.checkNotNull; 021import static com.google.common.collect.Collections2.transform; 022import static com.google.common.collect.Iterators.transform; 023import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; 024import static java.util.Collections.unmodifiableCollection; 025import static java.util.Collections.unmodifiableMap; 026import static java.util.Collections.unmodifiableSet; 027import static java.util.Collections.unmodifiableSortedMap; 028import static java.util.Collections.unmodifiableSortedSet; 029 030import com.google.common.annotations.GwtCompatible; 031import com.google.common.annotations.GwtIncompatible; 032import com.google.common.annotations.J2ktIncompatible; 033import com.google.common.base.Function; 034import com.google.common.base.Supplier; 035import com.google.common.collect.Table.Cell; 036import java.io.Serializable; 037import java.util.Collection; 038import java.util.Collections; 039import java.util.Iterator; 040import java.util.Map; 041import java.util.Objects; 042import java.util.Set; 043import java.util.SortedMap; 044import java.util.SortedSet; 045import java.util.Spliterator; 046import java.util.function.BinaryOperator; 047import java.util.stream.Collector; 048import org.jspecify.annotations.Nullable; 049 050/** 051 * Provides static methods that involve a {@code Table}. 052 * 053 * <p>See the Guava User Guide article on <a href= 054 * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#tables">{@code Tables}</a>. 055 * 056 * @author Jared Levy 057 * @author Louis Wasserman 058 * @since 7.0 059 */ 060@GwtCompatible 061public final class Tables { 062 private Tables() {} 063 064 /** 065 * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the 066 * specified supplier, whose cells are generated by applying the provided mapping functions to the 067 * input elements. Cells are inserted into the generated {@code Table} in encounter order. 068 * 069 * <p>If multiple input elements map to the same row and column, an {@code IllegalStateException} 070 * is thrown when the collection operation is performed. 071 * 072 * <p>To collect to an {@link ImmutableTable}, use {@link ImmutableTable#toImmutableTable}. 073 * 074 * @since 21.0 075 */ 076 public static < 077 T extends @Nullable Object, 078 R extends @Nullable Object, 079 C extends @Nullable Object, 080 V, 081 I extends Table<R, C, V>> 082 Collector<T, ?, I> toTable( 083 java.util.function.Function<? super T, ? extends R> rowFunction, 084 java.util.function.Function<? super T, ? extends C> columnFunction, 085 java.util.function.Function<? super T, ? extends V> valueFunction, 086 java.util.function.Supplier<I> tableSupplier) { 087 return TableCollectors.<T, R, C, V, I>toTable( 088 rowFunction, columnFunction, valueFunction, tableSupplier); 089 } 090 091 /** 092 * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the 093 * specified supplier, whose cells are generated by applying the provided mapping functions to the 094 * input elements. Cells are inserted into the generated {@code Table} in encounter order. 095 * 096 * <p>If multiple input elements map to the same row and column, the specified merging function is 097 * used to combine the values. Like {@link 098 * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, 099 * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code 100 * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls 101 * returned from {@code mergeFunction} as removals of that row/column pair. 102 * 103 * @since 21.0 104 */ 105 public static < 106 T extends @Nullable Object, 107 R extends @Nullable Object, 108 C extends @Nullable Object, 109 V, 110 I extends Table<R, C, V>> 111 Collector<T, ?, I> toTable( 112 java.util.function.Function<? super T, ? extends R> rowFunction, 113 java.util.function.Function<? super T, ? extends C> columnFunction, 114 java.util.function.Function<? super T, ? extends V> valueFunction, 115 BinaryOperator<V> mergeFunction, 116 java.util.function.Supplier<I> tableSupplier) { 117 return TableCollectors.<T, R, C, V, I>toTable( 118 rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier); 119 } 120 121 /** 122 * Returns an immutable cell with the specified row key, column key, and value. 123 * 124 * <p>The returned cell is serializable. 125 * 126 * @param rowKey the row key to be associated with the returned cell 127 * @param columnKey the column key to be associated with the returned cell 128 * @param value the value to be associated with the returned cell 129 */ 130 public static <R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 131 Cell<R, C, V> immutableCell( 132 @ParametricNullness R rowKey, 133 @ParametricNullness C columnKey, 134 @ParametricNullness V value) { 135 return new ImmutableCell<>(rowKey, columnKey, value); 136 } 137 138 static final class ImmutableCell< 139 R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 140 extends AbstractCell<R, C, V> implements Serializable { 141 @ParametricNullness private final R rowKey; 142 @ParametricNullness private final C columnKey; 143 @ParametricNullness private final V value; 144 145 ImmutableCell( 146 @ParametricNullness R rowKey, 147 @ParametricNullness C columnKey, 148 @ParametricNullness V value) { 149 this.rowKey = rowKey; 150 this.columnKey = columnKey; 151 this.value = value; 152 } 153 154 @Override 155 @ParametricNullness 156 public R getRowKey() { 157 return rowKey; 158 } 159 160 @Override 161 @ParametricNullness 162 public C getColumnKey() { 163 return columnKey; 164 } 165 166 @Override 167 @ParametricNullness 168 public V getValue() { 169 return value; 170 } 171 172 @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; 173 } 174 175 abstract static class AbstractCell< 176 R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 177 implements Cell<R, C, V> { 178 // needed for serialization 179 AbstractCell() {} 180 181 @Override 182 public boolean equals(@Nullable Object obj) { 183 if (obj == this) { 184 return true; 185 } 186 if (obj instanceof Cell) { 187 Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj; 188 return Objects.equals(getRowKey(), other.getRowKey()) 189 && Objects.equals(getColumnKey(), other.getColumnKey()) 190 && Objects.equals(getValue(), other.getValue()); 191 } 192 return false; 193 } 194 195 @Override 196 public int hashCode() { 197 return Objects.hash(getRowKey(), getColumnKey(), getValue()); 198 } 199 200 @Override 201 public String toString() { 202 return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); 203 } 204 } 205 206 /** 207 * Creates a transposed view of a given table that flips its row and column keys. In other words, 208 * calling {@code get(columnKey, rowKey)} on the generated table always returns the same value as 209 * calling {@code get(rowKey, columnKey)} on the original table. Updating the original table 210 * changes the contents of the transposed table and vice versa. 211 * 212 * <p>The returned table supports update operations as long as the input table supports the 213 * analogous operation with swapped rows and columns. For example, in a {@link HashBasedTable} 214 * instance, {@code rowKeySet().iterator()} supports {@code remove()} but {@code 215 * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other 216 * way around. 217 */ 218 public static <R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 219 Table<C, R, V> transpose(Table<R, C, V> table) { 220 return (table instanceof TransposeTable) 221 ? ((TransposeTable<R, C, V>) table).original 222 : new TransposeTable<C, R, V>(table); 223 } 224 225 private static final class TransposeTable< 226 C extends @Nullable Object, R extends @Nullable Object, V extends @Nullable Object> 227 extends AbstractTable<C, R, V> { 228 final Table<R, C, V> original; 229 230 TransposeTable(Table<R, C, V> original) { 231 this.original = checkNotNull(original); 232 } 233 234 @Override 235 public void clear() { 236 original.clear(); 237 } 238 239 @Override 240 public Map<C, V> column(@ParametricNullness R columnKey) { 241 return original.row(columnKey); 242 } 243 244 @Override 245 public Set<R> columnKeySet() { 246 return original.rowKeySet(); 247 } 248 249 @Override 250 public Map<R, Map<C, V>> columnMap() { 251 return original.rowMap(); 252 } 253 254 @Override 255 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { 256 return original.contains(columnKey, rowKey); 257 } 258 259 @Override 260 public boolean containsColumn(@Nullable Object columnKey) { 261 return original.containsRow(columnKey); 262 } 263 264 @Override 265 public boolean containsRow(@Nullable Object rowKey) { 266 return original.containsColumn(rowKey); 267 } 268 269 @Override 270 public boolean containsValue(@Nullable Object value) { 271 return original.containsValue(value); 272 } 273 274 @Override 275 public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { 276 return original.get(columnKey, rowKey); 277 } 278 279 @Override 280 public @Nullable V put( 281 @ParametricNullness C rowKey, 282 @ParametricNullness R columnKey, 283 @ParametricNullness V value) { 284 return original.put(columnKey, rowKey, value); 285 } 286 287 @Override 288 public void putAll(Table<? extends C, ? extends R, ? extends V> table) { 289 original.putAll(transpose(table)); 290 } 291 292 @Override 293 public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { 294 return original.remove(columnKey, rowKey); 295 } 296 297 @Override 298 public Map<R, V> row(@ParametricNullness C rowKey) { 299 return original.column(rowKey); 300 } 301 302 @Override 303 public Set<C> rowKeySet() { 304 return original.columnKeySet(); 305 } 306 307 @Override 308 public Map<C, Map<R, V>> rowMap() { 309 return original.columnMap(); 310 } 311 312 @Override 313 public int size() { 314 return original.size(); 315 } 316 317 @Override 318 public Collection<V> values() { 319 return original.values(); 320 } 321 322 @Override 323 Iterator<Cell<C, R, V>> cellIterator() { 324 return transform(original.cellSet().iterator(), Tables::transposeCell); 325 } 326 327 @Override 328 @GwtIncompatible // Spliterator 329 Spliterator<Cell<C, R, V>> cellSpliterator() { 330 return CollectSpliterators.map( 331 original.cellSet().spliterator(), 332 Spliterator.DISTINCT | Spliterator.NONNULL, 333 Tables::transposeCell); 334 } 335 } 336 337 private static < 338 R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 339 Cell<C, R, V> transposeCell(Cell<R, C, V> cell) { 340 return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); 341 } 342 343 /** 344 * Creates a table that uses the specified backing map and factory. It can generate a table based 345 * on arbitrary {@link Map} classes. 346 * 347 * <p>The {@code factory}-generated and {@code backingMap} classes determine the table iteration 348 * order. However, the table's {@code row()} method returns instances of a different class than 349 * {@code factory.get()} does. 350 * 351 * <p>Call this method only when the simpler factory methods in classes like {@link 352 * HashBasedTable} and {@link TreeBasedTable} won't suffice. 353 * 354 * <p>The views returned by the {@code Table} methods {@link Table#column}, {@link 355 * Table#columnKeySet}, and {@link Table#columnMap} have iterators that don't support {@code 356 * remove()}. Otherwise, all optional operations are supported. Null row keys, columns keys, and 357 * values are not supported. 358 * 359 * <p>Lookups by row key are often faster than lookups by column key, because the data is stored 360 * in a {@code Map<R, Map<C, V>>}. A method call like {@code column(columnKey).get(rowKey)} still 361 * runs quickly, since the row key is provided. However, {@code column(columnKey).size()} takes 362 * longer, since an iteration across all row keys occurs. 363 * 364 * <p>Note that this implementation is not synchronized. If multiple threads access this table 365 * concurrently and one of the threads modifies the table, it must be synchronized externally. 366 * 367 * <p>The table is serializable if {@code backingMap}, {@code factory}, the maps generated by 368 * {@code factory}, and the table contents are all serializable. 369 * 370 * <p>Note: the table assumes complete ownership over of {@code backingMap} and the maps returned 371 * by {@code factory}. Those objects should not be manually updated and they should not use soft, 372 * weak, or phantom references. 373 * 374 * @param backingMap place to store the mapping from each row key to its corresponding column key 375 * / value map 376 * @param factory supplier of new, empty maps that will each hold all column key / value mappings 377 * for a given row key 378 * @throws IllegalArgumentException if {@code backingMap} is not empty 379 * @since 10.0 380 */ 381 public static <R, C, V> Table<R, C, V> newCustomTable( 382 Map<R, Map<C, V>> backingMap, Supplier<? extends Map<C, V>> factory) { 383 checkArgument(backingMap.isEmpty()); 384 checkNotNull(factory); 385 // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? 386 return new StandardTable<>(backingMap, factory); 387 } 388 389 /** 390 * Returns a view of a table where each value is transformed by a function. All other properties 391 * of the table, such as iteration order, are left intact. 392 * 393 * <p>Changes in the underlying table are reflected in this view. Conversely, this view supports 394 * removal operations, and these are reflected in the underlying table. 395 * 396 * <p>It's acceptable for the underlying table to contain null keys, and even null values provided 397 * that the function is capable of accepting null input. The transformed table might contain null 398 * values, if the function sometimes gives a null result. 399 * 400 * <p>The returned table is not thread-safe or serializable, even if the underlying table is. 401 * 402 * <p>The function is applied lazily, invoked when needed. This is necessary for the returned 403 * table to be a view, but it means that the function will be applied many times for bulk 404 * operations like {@link Table#containsValue} and {@code Table.toString()}. For this to perform 405 * well, {@code function} should be fast. To avoid lazy evaluation when the returned table doesn't 406 * need to be a view, copy the returned table into a new table of your choosing. 407 * 408 * @since 10.0 409 */ 410 public static < 411 R extends @Nullable Object, 412 C extends @Nullable Object, 413 V1 extends @Nullable Object, 414 V2 extends @Nullable Object> 415 Table<R, C, V2> transformValues( 416 Table<R, C, V1> fromTable, Function<? super V1, V2> function) { 417 return new TransformedTable<>(fromTable, function); 418 } 419 420 private static final class TransformedTable< 421 R extends @Nullable Object, 422 C extends @Nullable Object, 423 V1 extends @Nullable Object, 424 V2 extends @Nullable Object> 425 extends AbstractTable<R, C, V2> { 426 final Table<R, C, V1> fromTable; 427 final Function<? super V1, V2> function; 428 429 TransformedTable(Table<R, C, V1> fromTable, Function<? super V1, V2> function) { 430 this.fromTable = checkNotNull(fromTable); 431 this.function = checkNotNull(function); 432 } 433 434 @Override 435 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { 436 return fromTable.contains(rowKey, columnKey); 437 } 438 439 @Override 440 public @Nullable V2 get(@Nullable Object rowKey, @Nullable Object columnKey) { 441 // The function is passed a null input only when the table contains a null 442 // value. 443 // The cast is safe because of the contains() check. 444 return contains(rowKey, columnKey) 445 ? function.apply(uncheckedCastNullableTToT(fromTable.get(rowKey, columnKey))) 446 : null; 447 } 448 449 @Override 450 public int size() { 451 return fromTable.size(); 452 } 453 454 @Override 455 public void clear() { 456 fromTable.clear(); 457 } 458 459 @Override 460 public @Nullable V2 put( 461 @ParametricNullness R rowKey, 462 @ParametricNullness C columnKey, 463 @ParametricNullness V2 value) { 464 throw new UnsupportedOperationException(); 465 } 466 467 @Override 468 public void putAll(Table<? extends R, ? extends C, ? extends V2> table) { 469 throw new UnsupportedOperationException(); 470 } 471 472 @Override 473 public @Nullable V2 remove(@Nullable Object rowKey, @Nullable Object columnKey) { 474 return contains(rowKey, columnKey) 475 // The cast is safe because of the contains() check. 476 ? function.apply(uncheckedCastNullableTToT(fromTable.remove(rowKey, columnKey))) 477 : null; 478 } 479 480 @Override 481 public Map<C, V2> row(@ParametricNullness R rowKey) { 482 return Maps.transformValues(fromTable.row(rowKey), function); 483 } 484 485 @Override 486 public Map<R, V2> column(@ParametricNullness C columnKey) { 487 return Maps.transformValues(fromTable.column(columnKey), function); 488 } 489 490 Cell<R, C, V2> applyToValue(Cell<R, C, V1> cell) { 491 return immutableCell(cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); 492 } 493 494 @Override 495 Iterator<Cell<R, C, V2>> cellIterator() { 496 return transform(fromTable.cellSet().iterator(), this::applyToValue); 497 } 498 499 @Override 500 @GwtIncompatible // Spliterator 501 Spliterator<Cell<R, C, V2>> cellSpliterator() { 502 return CollectSpliterators.map( 503 fromTable.cellSet().spliterator(), 504 Spliterator.NONNULL | Spliterator.DISTINCT, 505 this::applyToValue); 506 } 507 508 @Override 509 public Set<R> rowKeySet() { 510 return fromTable.rowKeySet(); 511 } 512 513 @Override 514 public Set<C> columnKeySet() { 515 return fromTable.columnKeySet(); 516 } 517 518 @Override 519 Collection<V2> createValues() { 520 return transform(fromTable.values(), function); 521 } 522 523 @Override 524 public Map<R, Map<C, V2>> rowMap() { 525 return Maps.transformValues(fromTable.rowMap(), row -> Maps.transformValues(row, function)); 526 } 527 528 @Override 529 public Map<C, Map<R, V2>> columnMap() { 530 return Maps.transformValues( 531 fromTable.columnMap(), column -> Maps.transformValues(column, function)); 532 } 533 } 534 535 /** 536 * Returns an unmodifiable view of the specified table. This method allows modules to provide 537 * users with "read-only" access to internal tables. Query operations on the returned table "read 538 * through" to the specified table, and attempts to modify the returned table, whether direct or 539 * via its collection views, result in an {@code UnsupportedOperationException}. 540 * 541 * <p>The returned table will be serializable if the specified table is serializable. 542 * 543 * <p>Consider using an {@link ImmutableTable}, which is guaranteed never to change. 544 * 545 * @since 11.0 546 */ 547 public static <R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 548 Table<R, C, V> unmodifiableTable(Table<? extends R, ? extends C, ? extends V> table) { 549 return new UnmodifiableTable<>(table); 550 } 551 552 private static class UnmodifiableTable< 553 R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 554 extends ForwardingTable<R, C, V> implements Serializable { 555 final Table<? extends R, ? extends C, ? extends V> delegate; 556 557 UnmodifiableTable(Table<? extends R, ? extends C, ? extends V> delegate) { 558 this.delegate = checkNotNull(delegate); 559 } 560 561 @SuppressWarnings("unchecked") // safe, covariant cast 562 @Override 563 protected Table<R, C, V> delegate() { 564 return (Table<R, C, V>) delegate; 565 } 566 567 @Override 568 public Set<Cell<R, C, V>> cellSet() { 569 return unmodifiableSet(super.cellSet()); 570 } 571 572 @Override 573 public void clear() { 574 throw new UnsupportedOperationException(); 575 } 576 577 @Override 578 public Map<R, V> column(@ParametricNullness C columnKey) { 579 return unmodifiableMap(super.column(columnKey)); 580 } 581 582 @Override 583 public Set<C> columnKeySet() { 584 return unmodifiableSet(super.columnKeySet()); 585 } 586 587 @Override 588 public Map<C, Map<R, V>> columnMap() { 589 return unmodifiableMap(Maps.transformValues(super.columnMap(), Collections::unmodifiableMap)); 590 } 591 592 @Override 593 public @Nullable V put( 594 @ParametricNullness R rowKey, 595 @ParametricNullness C columnKey, 596 @ParametricNullness V value) { 597 throw new UnsupportedOperationException(); 598 } 599 600 @Override 601 public void putAll(Table<? extends R, ? extends C, ? extends V> table) { 602 throw new UnsupportedOperationException(); 603 } 604 605 @Override 606 public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { 607 throw new UnsupportedOperationException(); 608 } 609 610 @Override 611 public Map<C, V> row(@ParametricNullness R rowKey) { 612 return unmodifiableMap(super.row(rowKey)); 613 } 614 615 @Override 616 public Set<R> rowKeySet() { 617 return unmodifiableSet(super.rowKeySet()); 618 } 619 620 @Override 621 public Map<R, Map<C, V>> rowMap() { 622 return unmodifiableMap(Maps.transformValues(super.rowMap(), Collections::unmodifiableMap)); 623 } 624 625 @Override 626 public Collection<V> values() { 627 return unmodifiableCollection(super.values()); 628 } 629 630 @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; 631 } 632 633 /** 634 * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to 635 * provide users with "read-only" access to internal tables. Query operations on the returned 636 * table "read through" to the specified table, and attempts to modify the returned table, whether 637 * direct or via its collection views, result in an {@code UnsupportedOperationException}. 638 * 639 * <p>The returned table will be serializable if the specified table is serializable. 640 * 641 * @param table the row-sorted table for which an unmodifiable view is to be returned 642 * @return an unmodifiable view of the specified table 643 * @since 11.0 644 */ 645 public static <R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 646 RowSortedTable<R, C, V> unmodifiableRowSortedTable( 647 RowSortedTable<R, ? extends C, ? extends V> table) { 648 /* 649 * It's not ? extends R, because it's technically not covariant in R. Specifically, 650 * table.rowMap().comparator() could return a comparator that only works for the ? extends R. 651 * Collections.unmodifiableSortedMap makes the same distinction. 652 */ 653 return new UnmodifiableRowSortedMap<>(table); 654 } 655 656 private static final class UnmodifiableRowSortedMap< 657 R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 658 extends UnmodifiableTable<R, C, V> implements RowSortedTable<R, C, V> { 659 660 UnmodifiableRowSortedMap(RowSortedTable<R, ? extends C, ? extends V> delegate) { 661 super(delegate); 662 } 663 664 @Override 665 protected RowSortedTable<R, C, V> delegate() { 666 return (RowSortedTable<R, C, V>) super.delegate(); 667 } 668 669 @Override 670 public SortedMap<R, Map<C, V>> rowMap() { 671 return unmodifiableSortedMap( 672 Maps.transformValues(delegate().rowMap(), Collections::unmodifiableMap)); 673 } 674 675 @Override 676 public SortedSet<R> rowKeySet() { 677 return unmodifiableSortedSet(delegate().rowKeySet()); 678 } 679 680 @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; 681 } 682 683 /** 684 * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee 685 * serial access, it is critical that <b>all</b> access to the backing table is accomplished 686 * through the returned table. 687 * 688 * <p>It is imperative that the user manually synchronize on the returned table when accessing any 689 * of its collection views: 690 * 691 * {@snippet : 692 * Table<R, C, V> table = Tables.synchronizedTable(HashBasedTable.create()); 693 * ... 694 * Map<C, V> row = table.row(rowKey); // Needn't be in synchronized block 695 * ... 696 * synchronized (table) { // Synchronizing on table, not row! 697 * Iterator<Entry<C, V>> i = row.entrySet().iterator(); // Must be in synchronized block 698 * while (i.hasNext()) { 699 * foo(i.next()); 700 * } 701 * } 702 * } 703 * 704 * <p>Failure to follow this advice may result in non-deterministic behavior. 705 * 706 * <p>The returned table will be serializable if the specified table is serializable. 707 * 708 * @param table the table to be wrapped in a synchronized view 709 * @return a synchronized view of the specified table 710 * @since 22.0 711 */ 712 @J2ktIncompatible // Synchronized 713 public static <R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> 714 Table<R, C, V> synchronizedTable(Table<R, C, V> table) { 715 return Synchronized.table(table, null); 716 } 717 718 @SuppressWarnings("ReferenceEquality") // == fast path 719 static boolean equalsImpl(Table<?, ?, ?> table, @Nullable Object obj) { 720 if (obj == table) { 721 return true; 722 } else if (obj instanceof Table) { 723 Table<?, ?, ?> that = (Table<?, ?, ?>) obj; 724 return table.cellSet().equals(that.cellSet()); 725 } else { 726 return false; 727 } 728 } 729}