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