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