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