Coverage Summary for Class: Tables (com.google.common.collect)

Class Method, % Line, %
Tables 28.6% (4/14) 39.1% (9/23)
Tables$1 50% (1/2) 50% (1/2)
Tables$AbstractCell 50% (2/4) 18.2% (2/11)
Tables$ImmutableCell 100% (4/4) 100% (8/8)
Tables$TransformedTable 0% (0/18) 0% (0/24)
Tables$TransformedTable$1 0% (0/2) 0% (0/3)
Tables$TransformedTable$2 0% (0/2) 0% (0/2)
Tables$TransformedTable$3 0% (0/2) 0% (0/2)
Tables$TransposeTable 0% (0/21) 0% (0/22)
Tables$TransposeTable$1 0% (0/2) 0% (0/2)
Tables$UnmodifiableRowSortedMap 50% (2/4) 50% (3/6)
Tables$UnmodifiableTable 14.3% (2/14) 22.2% (4/18)
Total 16.9% (15/89) 22% (27/123)


1 /* 2  * Copyright (C) 2008 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.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21  22 import com.google.common.annotations.Beta; 23 import com.google.common.annotations.GwtCompatible; 24 import com.google.common.base.Function; 25 import com.google.common.base.Objects; 26 import com.google.common.base.Supplier; 27 import com.google.common.collect.Table.Cell; 28 import java.io.Serializable; 29 import java.util.Collection; 30 import java.util.Collections; 31 import java.util.Iterator; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.SortedMap; 35 import java.util.SortedSet; 36 import java.util.Spliterator; 37 import java.util.function.BinaryOperator; 38 import java.util.stream.Collector; 39 import org.checkerframework.checker.nullness.qual.Nullable; 40  41 /** 42  * Provides static methods that involve a {@code Table}. 43  * 44  * <p>See the Guava User Guide article on <a href= 45  * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#tables"> {@code Tables}</a>. 46  * 47  * @author Jared Levy 48  * @author Louis Wasserman 49  * @since 7.0 50  */ 51 @GwtCompatible 52 public final class Tables { 53  private Tables() {} 54  55  /** 56  * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the 57  * specified supplier, whose cells are generated by applying the provided mapping functions to the 58  * input elements. Cells are inserted into the generated {@code Table} in encounter order. 59  * 60  * <p>If multiple input elements map to the same row and column, an {@code IllegalStateException} 61  * is thrown when the collection operation is performed. 62  * 63  * <p>To collect to an {@link ImmutableTable}, use {@link ImmutableTable#toImmutableTable}. 64  * 65  * @since 21.0 66  */ 67  @Beta 68  public static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable( 69  java.util.function.Function<? super T, ? extends R> rowFunction, 70  java.util.function.Function<? super T, ? extends C> columnFunction, 71  java.util.function.Function<? super T, ? extends V> valueFunction, 72  java.util.function.Supplier<I> tableSupplier) { 73  return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, tableSupplier); 74  } 75  76  /** 77  * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the 78  * specified supplier, whose cells are generated by applying the provided mapping functions to the 79  * input elements. Cells are inserted into the generated {@code Table} in encounter order. 80  * 81  * <p>If multiple input elements map to the same row and column, the specified merging function is 82  * used to combine the values. Like {@link 83  * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, 84  * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code 85  * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls 86  * returned from {@code mergeFunction} as removals of that row/column pair. 87  * 88  * @since 21.0 89  */ 90  public static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable( 91  java.util.function.Function<? super T, ? extends R> rowFunction, 92  java.util.function.Function<? super T, ? extends C> columnFunction, 93  java.util.function.Function<? super T, ? extends V> valueFunction, 94  BinaryOperator<V> mergeFunction, 95  java.util.function.Supplier<I> tableSupplier) { 96  return TableCollectors.toTable( 97  rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier); 98  } 99  100  /** 101  * Returns an immutable cell with the specified row key, column key, and value. 102  * 103  * <p>The returned cell is serializable. 104  * 105  * @param rowKey the row key to be associated with the returned cell 106  * @param columnKey the column key to be associated with the returned cell 107  * @param value the value to be associated with the returned cell 108  */ 109  public static <R, C, V> Cell<R, C, V> immutableCell( 110  @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { 111  return new ImmutableCell<>(rowKey, columnKey, value); 112  } 113  114  static final class ImmutableCell<R, C, V> extends AbstractCell<R, C, V> implements Serializable { 115  private final @Nullable R rowKey; 116  private final @Nullable C columnKey; 117  private final @Nullable V value; 118  119  ImmutableCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { 120  this.rowKey = rowKey; 121  this.columnKey = columnKey; 122  this.value = value; 123  } 124  125  @Override 126  public R getRowKey() { 127  return rowKey; 128  } 129  130  @Override 131  public C getColumnKey() { 132  return columnKey; 133  } 134  135  @Override 136  public V getValue() { 137  return value; 138  } 139  140  private static final long serialVersionUID = 0; 141  } 142  143  abstract static class AbstractCell<R, C, V> implements Cell<R, C, V> { 144  // needed for serialization 145  AbstractCell() {} 146  147  @Override 148  public boolean equals(Object obj) { 149  if (obj == this) { 150  return true; 151  } 152  if (obj instanceof Cell) { 153  Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj; 154  return Objects.equal(getRowKey(), other.getRowKey()) 155  && Objects.equal(getColumnKey(), other.getColumnKey()) 156  && Objects.equal(getValue(), other.getValue()); 157  } 158  return false; 159  } 160  161  @Override 162  public int hashCode() { 163  return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); 164  } 165  166  @Override 167  public String toString() { 168  return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); 169  } 170  } 171  172  /** 173  * Creates a transposed view of a given table that flips its row and column keys. In other words, 174  * calling {@code get(columnKey, rowKey)} on the generated table always returns the same value as 175  * calling {@code get(rowKey, columnKey)} on the original table. Updating the original table 176  * changes the contents of the transposed table and vice versa. 177  * 178  * <p>The returned table supports update operations as long as the input table supports the 179  * analogous operation with swapped rows and columns. For example, in a {@link HashBasedTable} 180  * instance, {@code rowKeySet().iterator()} supports {@code remove()} but {@code 181  * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other 182  * way around. 183  */ 184  public static <R, C, V> Table<C, R, V> transpose(Table<R, C, V> table) { 185  return (table instanceof TransposeTable) 186  ? ((TransposeTable<R, C, V>) table).original 187  : new TransposeTable<C, R, V>(table); 188  } 189  190  private static class TransposeTable<C, R, V> extends AbstractTable<C, R, V> { 191  final Table<R, C, V> original; 192  193  TransposeTable(Table<R, C, V> original) { 194  this.original = checkNotNull(original); 195  } 196  197  @Override 198  public void clear() { 199  original.clear(); 200  } 201  202  @Override 203  public Map<C, V> column(R columnKey) { 204  return original.row(columnKey); 205  } 206  207  @Override 208  public Set<R> columnKeySet() { 209  return original.rowKeySet(); 210  } 211  212  @Override 213  public Map<R, Map<C, V>> columnMap() { 214  return original.rowMap(); 215  } 216  217  @Override 218  public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { 219  return original.contains(columnKey, rowKey); 220  } 221  222  @Override 223  public boolean containsColumn(@Nullable Object columnKey) { 224  return original.containsRow(columnKey); 225  } 226  227  @Override 228  public boolean containsRow(@Nullable Object rowKey) { 229  return original.containsColumn(rowKey); 230  } 231  232  @Override 233  public boolean containsValue(@Nullable Object value) { 234  return original.containsValue(value); 235  } 236  237  @Override 238  public V get(@Nullable Object rowKey, @Nullable Object columnKey) { 239  return original.get(columnKey, rowKey); 240  } 241  242  @Override 243  public V put(C rowKey, R columnKey, V value) { 244  return original.put(columnKey, rowKey, value); 245  } 246  247  @Override 248  public void putAll(Table<? extends C, ? extends R, ? extends V> table) { 249  original.putAll(transpose(table)); 250  } 251  252  @Override 253  public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { 254  return original.remove(columnKey, rowKey); 255  } 256  257  @Override 258  public Map<R, V> row(C rowKey) { 259  return original.column(rowKey); 260  } 261  262  @Override 263  public Set<C> rowKeySet() { 264  return original.columnKeySet(); 265  } 266  267  @Override 268  public Map<C, Map<R, V>> rowMap() { 269  return original.columnMap(); 270  } 271  272  @Override 273  public int size() { 274  return original.size(); 275  } 276  277  @Override 278  public Collection<V> values() { 279  return original.values(); 280  } 281  282  // Will cast TRANSPOSE_CELL to a type that always succeeds 283  private static final Function<Cell<?, ?, ?>, Cell<?, ?, ?>> TRANSPOSE_CELL = 284  new Function<Cell<?, ?, ?>, Cell<?, ?, ?>>() { 285  @Override 286  public Cell<?, ?, ?> apply(Cell<?, ?, ?> cell) { 287  return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); 288  } 289  }; 290  291  @SuppressWarnings("unchecked") 292  @Override 293  Iterator<Cell<C, R, V>> cellIterator() { 294  return Iterators.transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); 295  } 296  297  @SuppressWarnings("unchecked") 298  @Override 299  Spliterator<Cell<C, R, V>> cellSpliterator() { 300  return CollectSpliterators.map(original.cellSet().spliterator(), (Function) TRANSPOSE_CELL); 301  } 302  } 303  304  /** 305  * Creates a table that uses the specified backing map and factory. It can generate a table based 306  * on arbitrary {@link Map} classes. 307  * 308  * <p>The {@code factory}-generated and {@code backingMap} classes determine the table iteration 309  * order. However, the table's {@code row()} method returns instances of a different class than 310  * {@code factory.get()} does. 311  * 312  * <p>Call this method only when the simpler factory methods in classes like {@link 313  * HashBasedTable} and {@link TreeBasedTable} won't suffice. 314  * 315  * <p>The views returned by the {@code Table} methods {@link Table#column}, {@link 316  * Table#columnKeySet}, and {@link Table#columnMap} have iterators that don't support {@code 317  * remove()}. Otherwise, all optional operations are supported. Null row keys, columns keys, and 318  * values are not supported. 319  * 320  * <p>Lookups by row key are often faster than lookups by column key, because the data is stored 321  * in a {@code Map<R, Map<C, V>>}. A method call like {@code column(columnKey).get(rowKey)} still 322  * runs quickly, since the row key is provided. However, {@code column(columnKey).size()} takes 323  * longer, since an iteration across all row keys occurs. 324  * 325  * <p>Note that this implementation is not synchronized. If multiple threads access this table 326  * concurrently and one of the threads modifies the table, it must be synchronized externally. 327  * 328  * <p>The table is serializable if {@code backingMap}, {@code factory}, the maps generated by 329  * {@code factory}, and the table contents are all serializable. 330  * 331  * <p>Note: the table assumes complete ownership over of {@code backingMap} and the maps returned 332  * by {@code factory}. Those objects should not be manually updated and they should not use soft, 333  * weak, or phantom references. 334  * 335  * @param backingMap place to store the mapping from each row key to its corresponding column key 336  * / value map 337  * @param factory supplier of new, empty maps that will each hold all column key / value mappings 338  * for a given row key 339  * @throws IllegalArgumentException if {@code backingMap} is not empty 340  * @since 10.0 341  */ 342  @Beta 343  public static <R, C, V> Table<R, C, V> newCustomTable( 344  Map<R, Map<C, V>> backingMap, Supplier<? extends Map<C, V>> factory) { 345  checkArgument(backingMap.isEmpty()); 346  checkNotNull(factory); 347  // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? 348  return new StandardTable<>(backingMap, factory); 349  } 350  351  /** 352  * Returns a view of a table where each value is transformed by a function. All other properties 353  * of the table, such as iteration order, are left intact. 354  * 355  * <p>Changes in the underlying table are reflected in this view. Conversely, this view supports 356  * removal operations, and these are reflected in the underlying table. 357  * 358  * <p>It's acceptable for the underlying table to contain null keys, and even null values provided 359  * that the function is capable of accepting null input. The transformed table might contain null 360  * values, if the function sometimes gives a null result. 361  * 362  * <p>The returned table is not thread-safe or serializable, even if the underlying table is. 363  * 364  * <p>The function is applied lazily, invoked when needed. This is necessary for the returned 365  * table to be a view, but it means that the function will be applied many times for bulk 366  * operations like {@link Table#containsValue} and {@code Table.toString()}. For this to perform 367  * well, {@code function} should be fast. To avoid lazy evaluation when the returned table doesn't 368  * need to be a view, copy the returned table into a new table of your choosing. 369  * 370  * @since 10.0 371  */ 372  @Beta 373  public static <R, C, V1, V2> Table<R, C, V2> transformValues( 374  Table<R, C, V1> fromTable, Function<? super V1, V2> function) { 375  return new TransformedTable<>(fromTable, function); 376  } 377  378  private static class TransformedTable<R, C, V1, V2> extends AbstractTable<R, C, V2> { 379  final Table<R, C, V1> fromTable; 380  final Function<? super V1, V2> function; 381  382  TransformedTable(Table<R, C, V1> fromTable, Function<? super V1, V2> function) { 383  this.fromTable = checkNotNull(fromTable); 384  this.function = checkNotNull(function); 385  } 386  387  @Override 388  public boolean contains(Object rowKey, Object columnKey) { 389  return fromTable.contains(rowKey, columnKey); 390  } 391  392  @Override 393  public V2 get(Object rowKey, Object columnKey) { 394  // The function is passed a null input only when the table contains a null 395  // value. 396  return contains(rowKey, columnKey) ? function.apply(fromTable.get(rowKey, columnKey)) : null; 397  } 398  399  @Override 400  public int size() { 401  return fromTable.size(); 402  } 403  404  @Override 405  public void clear() { 406  fromTable.clear(); 407  } 408  409  @Override 410  public V2 put(R rowKey, C columnKey, V2 value) { 411  throw new UnsupportedOperationException(); 412  } 413  414  @Override 415  public void putAll(Table<? extends R, ? extends C, ? extends V2> table) { 416  throw new UnsupportedOperationException(); 417  } 418  419  @Override 420  public V2 remove(Object rowKey, Object columnKey) { 421  return contains(rowKey, columnKey) 422  ? function.apply(fromTable.remove(rowKey, columnKey)) 423  : null; 424  } 425  426  @Override 427  public Map<C, V2> row(R rowKey) { 428  return Maps.transformValues(fromTable.row(rowKey), function); 429  } 430  431  @Override 432  public Map<R, V2> column(C columnKey) { 433  return Maps.transformValues(fromTable.column(columnKey), function); 434  } 435  436  Function<Cell<R, C, V1>, Cell<R, C, V2>> cellFunction() { 437  return new Function<Cell<R, C, V1>, Cell<R, C, V2>>() { 438  @Override 439  public Cell<R, C, V2> apply(Cell<R, C, V1> cell) { 440  return immutableCell( 441  cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); 442  } 443  }; 444  } 445  446  @Override 447  Iterator<Cell<R, C, V2>> cellIterator() { 448  return Iterators.transform(fromTable.cellSet().iterator(), cellFunction()); 449  } 450  451  @Override 452  Spliterator<Cell<R, C, V2>> cellSpliterator() { 453  return CollectSpliterators.map(fromTable.cellSet().spliterator(), cellFunction()); 454  } 455  456  @Override 457  public Set<R> rowKeySet() { 458  return fromTable.rowKeySet(); 459  } 460  461  @Override 462  public Set<C> columnKeySet() { 463  return fromTable.columnKeySet(); 464  } 465  466  @Override 467  Collection<V2> createValues() { 468  return Collections2.transform(fromTable.values(), function); 469  } 470  471  @Override 472  public Map<R, Map<C, V2>> rowMap() { 473  Function<Map<C, V1>, Map<C, V2>> rowFunction = 474  new Function<Map<C, V1>, Map<C, V2>>() { 475  @Override 476  public Map<C, V2> apply(Map<C, V1> row) { 477  return Maps.transformValues(row, function); 478  } 479  }; 480  return Maps.transformValues(fromTable.rowMap(), rowFunction); 481  } 482  483  @Override 484  public Map<C, Map<R, V2>> columnMap() { 485  Function<Map<R, V1>, Map<R, V2>> columnFunction = 486  new Function<Map<R, V1>, Map<R, V2>>() { 487  @Override 488  public Map<R, V2> apply(Map<R, V1> column) { 489  return Maps.transformValues(column, function); 490  } 491  }; 492  return Maps.transformValues(fromTable.columnMap(), columnFunction); 493  } 494  } 495  496  /** 497  * Returns an unmodifiable view of the specified table. This method allows modules to provide 498  * users with "read-only" access to internal tables. Query operations on the returned table "read 499  * through" to the specified table, and attempts to modify the returned table, whether direct or 500  * via its collection views, result in an {@code UnsupportedOperationException}. 501  * 502  * <p>The returned table will be serializable if the specified table is serializable. 503  * 504  * <p>Consider using an {@link ImmutableTable}, which is guaranteed never to change. 505  * 506  * @since 11.0 507  */ 508  public static <R, C, V> Table<R, C, V> unmodifiableTable( 509  Table<? extends R, ? extends C, ? extends V> table) { 510  return new UnmodifiableTable<>(table); 511  } 512  513  private static class UnmodifiableTable<R, C, V> extends ForwardingTable<R, C, V> 514  implements Serializable { 515  final Table<? extends R, ? extends C, ? extends V> delegate; 516  517  UnmodifiableTable(Table<? extends R, ? extends C, ? extends V> delegate) { 518  this.delegate = checkNotNull(delegate); 519  } 520  521  @SuppressWarnings("unchecked") // safe, covariant cast 522  @Override 523  protected Table<R, C, V> delegate() { 524  return (Table<R, C, V>) delegate; 525  } 526  527  @Override 528  public Set<Cell<R, C, V>> cellSet() { 529  return Collections.unmodifiableSet(super.cellSet()); 530  } 531  532  @Override 533  public void clear() { 534  throw new UnsupportedOperationException(); 535  } 536  537  @Override 538  public Map<R, V> column(@Nullable C columnKey) { 539  return Collections.unmodifiableMap(super.column(columnKey)); 540  } 541  542  @Override 543  public Set<C> columnKeySet() { 544  return Collections.unmodifiableSet(super.columnKeySet()); 545  } 546  547  @Override 548  public Map<C, Map<R, V>> columnMap() { 549  Function<Map<R, V>, Map<R, V>> wrapper = unmodifiableWrapper(); 550  return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); 551  } 552  553  @Override 554  public V put(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { 555  throw new UnsupportedOperationException(); 556  } 557  558  @Override 559  public void putAll(Table<? extends R, ? extends C, ? extends V> table) { 560  throw new UnsupportedOperationException(); 561  } 562  563  @Override 564  public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { 565  throw new UnsupportedOperationException(); 566  } 567  568  @Override 569  public Map<C, V> row(@Nullable R rowKey) { 570  return Collections.unmodifiableMap(super.row(rowKey)); 571  } 572  573  @Override 574  public Set<R> rowKeySet() { 575  return Collections.unmodifiableSet(super.rowKeySet()); 576  } 577  578  @Override 579  public Map<R, Map<C, V>> rowMap() { 580  Function<Map<C, V>, Map<C, V>> wrapper = unmodifiableWrapper(); 581  return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); 582  } 583  584  @Override 585  public Collection<V> values() { 586  return Collections.unmodifiableCollection(super.values()); 587  } 588  589  private static final long serialVersionUID = 0; 590  } 591  592  /** 593  * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to 594  * provide users with "read-only" access to internal tables. Query operations on the returned 595  * table "read through" to the specified table, and attempts to modify the returned table, whether 596  * direct or via its collection views, result in an {@code UnsupportedOperationException}. 597  * 598  * <p>The returned table will be serializable if the specified table is serializable. 599  * 600  * @param table the row-sorted table for which an unmodifiable view is to be returned 601  * @return an unmodifiable view of the specified table 602  * @since 11.0 603  */ 604  @Beta 605  public static <R, C, V> RowSortedTable<R, C, V> unmodifiableRowSortedTable( 606  RowSortedTable<R, ? extends C, ? extends V> table) { 607  /* 608  * It's not ? extends R, because it's technically not covariant in R. Specifically, 609  * table.rowMap().comparator() could return a comparator that only works for the ? extends R. 610  * Collections.unmodifiableSortedMap makes the same distinction. 611  */ 612  return new UnmodifiableRowSortedMap<>(table); 613  } 614  615  static final class UnmodifiableRowSortedMap<R, C, V> extends UnmodifiableTable<R, C, V> 616  implements RowSortedTable<R, C, V> { 617  618  public UnmodifiableRowSortedMap(RowSortedTable<R, ? extends C, ? extends V> delegate) { 619  super(delegate); 620  } 621  622  @Override 623  protected RowSortedTable<R, C, V> delegate() { 624  return (RowSortedTable<R, C, V>) super.delegate(); 625  } 626  627  @Override 628  public SortedMap<R, Map<C, V>> rowMap() { 629  Function<Map<C, V>, Map<C, V>> wrapper = unmodifiableWrapper(); 630  return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); 631  } 632  633  @Override 634  public SortedSet<R> rowKeySet() { 635  return Collections.unmodifiableSortedSet(delegate().rowKeySet()); 636  } 637  638  private static final long serialVersionUID = 0; 639  } 640  641  @SuppressWarnings("unchecked") 642  private static <K, V> Function<Map<K, V>, Map<K, V>> unmodifiableWrapper() { 643  return (Function) UNMODIFIABLE_WRAPPER; 644  } 645  646  private static final Function<? extends Map<?, ?>, ? extends Map<?, ?>> UNMODIFIABLE_WRAPPER = 647  new Function<Map<Object, Object>, Map<Object, Object>>() { 648  @Override 649  public Map<Object, Object> apply(Map<Object, Object> input) { 650  return Collections.unmodifiableMap(input); 651  } 652  }; 653  654  /** 655  * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee 656  * serial access, it is critical that <b>all</b> access to the backing table is accomplished 657  * through the returned table. 658  * 659  * <p>It is imperative that the user manually synchronize on the returned table when accessing any 660  * of its collection views: 661  * 662  * <pre>{@code 663  * Table<R, C, V> table = Tables.synchronizedTable(HashBasedTable.<R, C, V>create()); 664  * ... 665  * Map<C, V> row = table.row(rowKey); // Needn't be in synchronized block 666  * ... 667  * synchronized (table) { // Synchronizing on table, not row! 668  * Iterator<Entry<C, V>> i = row.entrySet().iterator(); // Must be in synchronized block 669  * while (i.hasNext()) { 670  * foo(i.next()); 671  * } 672  * } 673  * }</pre> 674  * 675  * <p>Failure to follow this advice may result in non-deterministic behavior. 676  * 677  * <p>The returned table will be serializable if the specified table is serializable. 678  * 679  * @param table the table to be wrapped in a synchronized view 680  * @return a synchronized view of the specified table 681  * @since 22.0 682  */ 683  public static <R, C, V> Table<R, C, V> synchronizedTable(Table<R, C, V> table) { 684  return Synchronized.table(table, null); 685  } 686  687  static boolean equalsImpl(Table<?, ?, ?> table, @Nullable Object obj) { 688  if (obj == table) { 689  return true; 690  } else if (obj instanceof Table) { 691  Table<?, ?, ?> that = (Table<?, ?, ?>) obj; 692  return table.cellSet().equals(that.cellSet()); 693  } else { 694  return false; 695  } 696  } 697 }