001 /* 002 * Copyright (C) 2009 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.base.Objects; 024 025 import java.io.Serializable; 026 import java.lang.reflect.Array; 027 import java.util.AbstractCollection; 028 import java.util.AbstractMap; 029 import java.util.AbstractSet; 030 import java.util.Arrays; 031 import java.util.Collection; 032 import java.util.Iterator; 033 import java.util.Map; 034 import java.util.Map.Entry; 035 import java.util.Set; 036 037 import javax.annotation.Nullable; 038 039 /** 040 * Fixed-size {@link Table} implementation backed by a two-dimensional array. 041 * 042 * <p>The allowed row and column keys must be supplied when the table is 043 * created. The table always contains a mapping for every row key / column pair. 044 * The value corresponding to a given row and column is null unless another 045 * value is provided. 046 * 047 * <p>The table's size is constant: the product of the number of supplied row 048 * keys and the number of supplied column keys. The {@code remove} and {@code 049 * clear} methods are not supported by the table or its views. The {@link 050 * #erase} and {@link #eraseAll} methods may be used instead. 051 * 052 * <p>The ordering of the row and column keys provided when the table is 053 * constructed determines the iteration ordering across rows and columns in the 054 * table's views. None of the view iterators support {@link Iterator#remove}. 055 * If the table is modified after an iterator is created, the iterator remains 056 * valid. 057 * 058 * <p>This class requires less memory than the {@link HashBasedTable} and {@link 059 * TreeBasedTable} implementations, except when the table is sparse. 060 * 061 * <p>Null row keys or column keys are not permitted. 062 * 063 * <p>This class provides methods involving the underlying array structure, 064 * where the array indices correspond to the position of a row or column in the 065 * lists of allowed keys and values. See the {@link #at}, {@link #set}, {@link 066 * #toArray}, {@link #rowKeyList}, and {@link #columnKeyList} methods for more 067 * details. 068 * 069 * <p>Note that this implementation is not synchronized. If multiple threads 070 * access the same cell of an {@code ArrayTable} concurrently and one of the 071 * threads modifies its value, there is no guarantee that the new value will be 072 * fully visible to the other threads. To guarantee that modifications are 073 * visible, synchronize access to the table. Unlike other {@code Table} 074 * implementations, synchronization is unnecessary between a thread that writes 075 * to one cell and a thread that reads from another. 076 * 077 * <p>See the Guava User Guide article on <a href= 078 * "http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Table"> 079 * {@code Table}</a>. 080 * 081 * @author Jared Levy 082 * @since 10.0 083 */ 084 @Beta 085 public final class ArrayTable<R, C, V> implements Table<R, C, V>, Serializable { 086 087 /** 088 * Creates an empty {@code ArrayTable}. 089 * 090 * @param rowKeys row keys that may be stored in the generated table 091 * @param columnKeys column keys that may be stored in the generated table 092 * @throws NullPointerException if any of the provided keys is null 093 * @throws IllegalArgumentException if {@code rowKeys} or {@code columnKeys} 094 * contains duplicates or is empty 095 */ 096 public static <R, C, V> ArrayTable<R, C, V> create( 097 Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys) { 098 return new ArrayTable<R, C, V>(rowKeys, columnKeys); 099 } 100 101 /* 102 * TODO(jlevy): Add factory methods taking an Enum class, instead of an 103 * iterable, to specify the allowed row keys and/or column keys. Note that 104 * custom serialization logic is needed to support different enum sizes during 105 * serialization and deserialization. 106 */ 107 108 /** 109 * Creates an {@code ArrayTable} with the mappings in the provided table. 110 * 111 * <p>If {@code table} includes a mapping with row key {@code r} and a 112 * separate mapping with column key {@code c}, the returned table contains a 113 * mapping with row key {@code r} and column key {@code c}. If that row key / 114 * column key pair in not in {@code table}, the pair maps to {@code null} in 115 * the generated table. 116 * 117 * <p>The returned table allows subsequent {@code put} calls with the row keys 118 * in {@code table.rowKeySet()} and the column keys in {@code 119 * table.columnKeySet()}. Calling {@link #put} with other keys leads to an 120 * {@code IllegalArgumentException}. 121 * 122 * <p>The ordering of {@code table.rowKeySet()} and {@code 123 * table.columnKeySet()} determines the row and column iteration ordering of 124 * the returned table. 125 * 126 * @throws NullPointerException if {@code table} has a null key 127 * @throws IllegalArgumentException if the provided table is empty 128 */ 129 public static <R, C, V> ArrayTable<R, C, V> create(Table<R, C, V> table) { 130 return new ArrayTable<R, C, V>(table); 131 } 132 133 /** 134 * Creates an {@code ArrayTable} with the same mappings, allowed keys, and 135 * iteration ordering as the provided {@code ArrayTable}. 136 */ 137 public static <R, C, V> ArrayTable<R, C, V> create( 138 ArrayTable<R, C, V> table) { 139 return new ArrayTable<R, C, V>(table); 140 } 141 142 private final ImmutableList<R> rowList; 143 private final ImmutableList<C> columnList; 144 145 // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex? 146 private final ImmutableMap<R, Integer> rowKeyToIndex; 147 private final ImmutableMap<C, Integer> columnKeyToIndex; 148 private final V[][] array; 149 150 private ArrayTable(Iterable<? extends R> rowKeys, 151 Iterable<? extends C> columnKeys) { 152 this.rowList = ImmutableList.copyOf(rowKeys); 153 this.columnList = ImmutableList.copyOf(columnKeys); 154 checkArgument(!rowList.isEmpty()); 155 checkArgument(!columnList.isEmpty()); 156 157 /* 158 * TODO(jlevy): Support empty rowKeys or columnKeys? If we do, when 159 * columnKeys is empty but rowKeys isn't, the table is empty but 160 * containsRow() can return true and rowKeySet() isn't empty. 161 */ 162 ImmutableMap.Builder<R, Integer> rowBuilder = ImmutableMap.builder(); 163 for (int i = 0; i < rowList.size(); i++) { 164 rowBuilder.put(rowList.get(i), i); 165 } 166 rowKeyToIndex = rowBuilder.build(); 167 168 ImmutableMap.Builder<C, Integer> columnBuilder = ImmutableMap.builder(); 169 for (int i = 0; i < columnList.size(); i++) { 170 columnBuilder.put(columnList.get(i), i); 171 } 172 columnKeyToIndex = columnBuilder.build(); 173 174 @SuppressWarnings("unchecked") 175 V[][] tmpArray 176 = (V[][]) new Object[rowList.size()][columnList.size()]; 177 array = tmpArray; 178 } 179 180 private ArrayTable(Table<R, C, V> table) { 181 this(table.rowKeySet(), table.columnKeySet()); 182 putAll(table); 183 } 184 185 private ArrayTable(ArrayTable<R, C, V> table) { 186 rowList = table.rowList; 187 columnList = table.columnList; 188 rowKeyToIndex = table.rowKeyToIndex; 189 columnKeyToIndex = table.columnKeyToIndex; 190 @SuppressWarnings("unchecked") 191 V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()]; 192 array = copy; 193 for (int i = 0; i < rowList.size(); i++) { 194 System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length); 195 } 196 } 197 198 /** 199 * Returns, as an immutable list, the row keys provided when the table was 200 * constructed, including those that are mapped to null values only. 201 */ 202 public ImmutableList<R> rowKeyList() { 203 return rowList; 204 } 205 206 /** 207 * Returns, as an immutable list, the column keys provided when the table was 208 * constructed, including those that are mapped to null values only. 209 */ 210 public ImmutableList<C> columnKeyList() { 211 return columnList; 212 } 213 214 /** 215 * Returns the value corresponding to the specified row and column indices. 216 * The same value is returned by {@code 217 * get(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex))}, but 218 * this method runs more quickly. 219 * 220 * @param rowIndex position of the row key in {@link #rowKeyList()} 221 * @param columnIndex position of the row key in {@link #columnKeyList()} 222 * @return the value with the specified row and column 223 * @throws IndexOutOfBoundsException if either index is negative, {@code 224 * rowIndex} is greater then or equal to the number of allowed row keys, 225 * or {@code columnIndex} is greater then or equal to the number of 226 * allowed column keys 227 */ 228 public V at(int rowIndex, int columnIndex) { 229 return array[rowIndex][columnIndex]; 230 } 231 232 /** 233 * Associates {@code value} with the specified row and column indices. The 234 * logic {@code 235 * put(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex), value)} 236 * has the same behavior, but this method runs more quickly. 237 * 238 * @param rowIndex position of the row key in {@link #rowKeyList()} 239 * @param columnIndex position of the row key in {@link #columnKeyList()} 240 * @param value value to store in the table 241 * @return the previous value with the specified row and column 242 * @throws IndexOutOfBoundsException if either index is negative, {@code 243 * rowIndex} is greater then or equal to the number of allowed row keys, 244 * or {@code columnIndex} is greater then or equal to the number of 245 * allowed column keys 246 */ 247 public V set(int rowIndex, int columnIndex, @Nullable V value) { 248 V oldValue = array[rowIndex][columnIndex]; 249 array[rowIndex][columnIndex] = value; 250 return oldValue; 251 } 252 253 /** 254 * Returns a two-dimensional array with the table contents. The row and column 255 * indices correspond to the positions of the row and column in the iterables 256 * provided during table construction. If the table lacks a mapping for a 257 * given row and column, the corresponding array element is null. 258 * 259 * <p>Subsequent table changes will not modify the array, and vice versa. 260 * 261 * @param valueClass class of values stored in the returned array 262 */ 263 public V[][] toArray(Class<V> valueClass) { 264 // Can change to use varargs in JDK 1.6 if we want 265 @SuppressWarnings("unchecked") // TODO: safe? 266 V[][] copy = (V[][]) Array.newInstance( 267 valueClass, new int[] { rowList.size(), columnList.size() }); 268 for (int i = 0; i < rowList.size(); i++) { 269 System.arraycopy(array[i], 0, copy[i], 0, array[i].length); 270 } 271 return copy; 272 } 273 274 /** 275 * Not supported. Use {@link #eraseAll} instead. 276 * 277 * @throws UnsupportedOperationException always 278 * @deprecated Use {@link #eraseAll} 279 */ 280 @Override 281 @Deprecated public void clear() { 282 throw new UnsupportedOperationException(); 283 } 284 285 /** 286 * Associates the value {@code null} with every pair of allowed row and column 287 * keys. 288 */ 289 public void eraseAll() { 290 for (V[] row : array) { 291 Arrays.fill(row, null); 292 } 293 } 294 295 /** 296 * Returns {@code true} if the provided keys are among the keys provided when 297 * the table was constructed. 298 */ 299 @Override 300 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { 301 return containsRow(rowKey) && containsColumn(columnKey); 302 } 303 304 /** 305 * Returns {@code true} if the provided column key is among the column keys 306 * provided when the table was constructed. 307 */ 308 @Override 309 public boolean containsColumn(@Nullable Object columnKey) { 310 return columnKeyToIndex.containsKey(columnKey); 311 } 312 313 /** 314 * Returns {@code true} if the provided row key is among the row keys 315 * provided when the table was constructed. 316 */ 317 @Override 318 public boolean containsRow(@Nullable Object rowKey) { 319 return rowKeyToIndex.containsKey(rowKey); 320 } 321 322 @Override 323 public boolean containsValue(@Nullable Object value) { 324 for (V[] row : array) { 325 for (V element : row) { 326 if (Objects.equal(value, element)) { 327 return true; 328 } 329 } 330 } 331 return false; 332 } 333 334 @Override 335 public V get(@Nullable Object rowKey, @Nullable Object columnKey) { 336 Integer rowIndex = rowKeyToIndex.get(rowKey); 337 Integer columnIndex = columnKeyToIndex.get(columnKey); 338 return getIndexed(rowIndex, columnIndex); 339 } 340 341 private V getIndexed(Integer rowIndex, Integer columnIndex) { 342 return (rowIndex == null || columnIndex == null) 343 ? null : array[rowIndex][columnIndex]; 344 } 345 346 /** 347 * Always returns {@code false}. 348 */ 349 @Override 350 public boolean isEmpty() { 351 return false; 352 } 353 354 /** 355 * {@inheritDoc} 356 * 357 * @throws IllegalArgumentException if {@code rowKey} is not in {@link 358 * #rowKeySet()} or {@code columnKey} is not in {@link #columnKeySet()}. 359 */ 360 @Override 361 public V put(R rowKey, C columnKey, @Nullable V value) { 362 checkNotNull(rowKey); 363 checkNotNull(columnKey); 364 Integer rowIndex = rowKeyToIndex.get(rowKey); 365 checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList); 366 Integer columnIndex = columnKeyToIndex.get(columnKey); 367 checkArgument(columnIndex != null, 368 "Column %s not in %s", columnKey, columnList); 369 return set(rowIndex, columnIndex, value); 370 } 371 372 /* 373 * TODO(jlevy): Consider creating a merge() method, similar to putAll() but 374 * copying non-null values only. 375 */ 376 377 /** 378 * {@inheritDoc} 379 * 380 * <p>If {@code table} is an {@code ArrayTable}, its null values will be 381 * stored in this table, possibly replacing values that were previously 382 * non-null. 383 * 384 * @throws NullPointerException if {@code table} has a null key 385 * @throws IllegalArgumentException if any of the provided table's row keys or 386 * column keys is not in {@link #rowKeySet()} or {@link #columnKeySet()} 387 */ 388 @Override 389 public void putAll(Table<? extends R, ? extends C, ? extends V> table) { 390 for (Cell<? extends R, ? extends C, ? extends V> cell : table.cellSet()) { 391 put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); 392 } 393 } 394 395 /** 396 * Not supported. Use {@link #erase} instead. 397 * 398 * @throws UnsupportedOperationException always 399 * @deprecated Use {@link #erase} 400 */ 401 @Override 402 @Deprecated public V remove(Object rowKey, Object columnKey) { 403 throw new UnsupportedOperationException(); 404 } 405 406 /** 407 * Associates the value {@code null} with the specified keys, assuming both 408 * keys are valid. If either key is null or isn't among the keys provided 409 * during construction, this method has no effect. 410 * 411 * <p>This method is equivalent to {@code put(rowKey, columnKey, null)} when 412 * both provided keys are valid. 413 * 414 * @param rowKey row key of mapping to be erased 415 * @param columnKey column key of mapping to be erased 416 * @return the value previously associated with the keys, or {@code null} if 417 * no mapping existed for the keys 418 */ 419 public V erase(@Nullable Object rowKey, @Nullable Object columnKey) { 420 Integer rowIndex = rowKeyToIndex.get(rowKey); 421 Integer columnIndex = columnKeyToIndex.get(columnKey); 422 if (rowIndex == null || columnIndex == null) { 423 return null; 424 } 425 return set(rowIndex, columnIndex, null); 426 } 427 428 // TODO(jlevy): Add eraseRow and eraseColumn methods? 429 430 @Override 431 public int size() { 432 return rowList.size() * columnList.size(); 433 } 434 435 @Override public boolean equals(@Nullable Object obj) { 436 if (obj instanceof Table) { 437 Table<?, ?, ?> other = (Table<?, ?, ?>) obj; 438 return cellSet().equals(other.cellSet()); 439 } 440 return false; 441 } 442 443 @Override public int hashCode() { 444 return cellSet().hashCode(); 445 } 446 447 /** 448 * Returns the string representation {@code rowMap().toString()}. 449 */ 450 @Override public String toString() { 451 return rowMap().toString(); 452 } 453 454 private transient CellSet cellSet; 455 456 /** 457 * Returns an unmodifiable set of all row key / column key / value 458 * triplets. Changes to the table will update the returned set. 459 * 460 * <p>The returned set's iterator traverses the mappings with the first row 461 * key, the mappings with the second row key, and so on. 462 * 463 * <p>The value in the returned cells may change if the table subsequently 464 * changes. 465 * 466 * @return set of table cells consisting of row key / column key / value 467 * triplets 468 */ 469 @Override 470 public Set<Cell<R, C, V>> cellSet() { 471 CellSet set = cellSet; 472 return (set == null) ? cellSet = new CellSet() : set; 473 } 474 475 private class CellSet extends AbstractSet<Cell<R, C, V>> { 476 477 @Override public Iterator<Cell<R, C, V>> iterator() { 478 return new AbstractIndexedListIterator<Cell<R, C, V>>(size()) { 479 @Override protected Cell<R, C, V> get(final int index) { 480 return new Tables.AbstractCell<R, C, V>() { 481 final int rowIndex = index / columnList.size(); 482 final int columnIndex = index % columnList.size(); 483 @Override 484 public R getRowKey() { 485 return rowList.get(rowIndex); 486 } 487 @Override 488 public C getColumnKey() { 489 return columnList.get(columnIndex); 490 } 491 @Override 492 public V getValue() { 493 return array[rowIndex][columnIndex]; 494 } 495 }; 496 } 497 }; 498 } 499 500 @Override public int size() { 501 return ArrayTable.this.size(); 502 } 503 504 @Override public boolean contains(Object obj) { 505 if (obj instanceof Cell) { 506 Cell<?, ?, ?> cell = (Cell<?, ?, ?>) obj; 507 Integer rowIndex = rowKeyToIndex.get(cell.getRowKey()); 508 Integer columnIndex = columnKeyToIndex.get(cell.getColumnKey()); 509 return rowIndex != null 510 && columnIndex != null 511 && Objects.equal(array[rowIndex][columnIndex], cell.getValue()); 512 } 513 return false; 514 } 515 } 516 517 /** 518 * Returns a view of all mappings that have the given column key. If the 519 * column key isn't in {@link #columnKeySet()}, an empty immutable map is 520 * returned. 521 * 522 * <p>Otherwise, for each row key in {@link #rowKeySet()}, the returned map 523 * associates the row key with the corresponding value in the table. Changes 524 * to the returned map will update the underlying table, and vice versa. 525 * 526 * @param columnKey key of column to search for in the table 527 * @return the corresponding map from row keys to values 528 */ 529 @Override 530 public Map<R, V> column(C columnKey) { 531 checkNotNull(columnKey); 532 Integer columnIndex = columnKeyToIndex.get(columnKey); 533 return (columnIndex == null) 534 ? ImmutableMap.<R, V>of() : new Column(columnIndex); 535 } 536 537 private class Column extends AbstractMap<R, V> { 538 final int columnIndex; 539 540 Column(int columnIndex) { 541 this.columnIndex = columnIndex; 542 } 543 544 ColumnEntrySet entrySet; 545 546 @Override public Set<Entry<R, V>> entrySet() { 547 ColumnEntrySet set = entrySet; 548 return (set == null) ? entrySet = new ColumnEntrySet(columnIndex) : set; 549 } 550 551 @Override public V get(Object rowKey) { 552 Integer rowIndex = rowKeyToIndex.get(rowKey); 553 return getIndexed(rowIndex, columnIndex); 554 } 555 556 @Override public boolean containsKey(Object rowKey) { 557 return rowKeyToIndex.containsKey(rowKey); 558 } 559 560 @Override public V put(R rowKey, V value) { 561 checkNotNull(rowKey); 562 Integer rowIndex = rowKeyToIndex.get(rowKey); 563 checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList); 564 return set(rowIndex, columnIndex, value); 565 } 566 567 @Override public Set<R> keySet() { 568 return rowKeySet(); 569 } 570 } 571 572 private class ColumnEntrySet extends AbstractSet<Entry<R, V>> { 573 final int columnIndex; 574 575 ColumnEntrySet(int columnIndex) { 576 this.columnIndex = columnIndex; 577 } 578 579 @Override public Iterator<Entry<R, V>> iterator() { 580 return new AbstractIndexedListIterator<Entry<R, V>>(size()) { 581 @Override protected Entry<R, V> get(final int rowIndex) { 582 return new AbstractMapEntry<R, V>() { 583 @Override public R getKey() { 584 return rowList.get(rowIndex); 585 } 586 @Override public V getValue() { 587 return array[rowIndex][columnIndex]; 588 } 589 @Override public V setValue(V value) { 590 return ArrayTable.this.set(rowIndex, columnIndex, value); 591 } 592 }; 593 } 594 }; 595 } 596 597 @Override public int size() { 598 return rowList.size(); 599 } 600 } 601 602 /** 603 * Returns an immutable set of the valid column keys, including those that 604 * are associated with null values only. 605 * 606 * @return immutable set of column keys 607 */ 608 @Override 609 public ImmutableSet<C> columnKeySet() { 610 return columnKeyToIndex.keySet(); 611 } 612 613 private transient ColumnMap columnMap; 614 615 @Override 616 public Map<C, Map<R, V>> columnMap() { 617 ColumnMap map = columnMap; 618 return (map == null) ? columnMap = new ColumnMap() : map; 619 } 620 621 private class ColumnMap extends AbstractMap<C, Map<R, V>> { 622 transient ColumnMapEntrySet entrySet; 623 624 @Override public Set<Entry<C, Map<R, V>>> entrySet() { 625 ColumnMapEntrySet set = entrySet; 626 return (set == null) ? entrySet = new ColumnMapEntrySet() : set; 627 } 628 629 @Override public Map<R, V> get(Object columnKey) { 630 Integer columnIndex = columnKeyToIndex.get(columnKey); 631 return (columnIndex == null) ? null : new Column(columnIndex); 632 } 633 634 @Override public boolean containsKey(Object columnKey) { 635 return containsColumn(columnKey); 636 } 637 638 @Override public Set<C> keySet() { 639 return columnKeySet(); 640 } 641 642 @Override public Map<R, V> remove(Object columnKey) { 643 throw new UnsupportedOperationException(); 644 } 645 } 646 647 private class ColumnMapEntrySet extends AbstractSet<Entry<C, Map<R, V>>> { 648 @Override public Iterator<Entry<C, Map<R, V>>> iterator() { 649 return new AbstractIndexedListIterator<Entry<C, Map<R, V>>>(size()) { 650 @Override protected Entry<C, Map<R, V>> get(int index) { 651 return Maps.<C, Map<R, V>>immutableEntry(columnList.get(index), 652 new Column(index)); 653 } 654 }; 655 } 656 657 @Override public int size() { 658 return columnList.size(); 659 } 660 } 661 662 /** 663 * Returns a view of all mappings that have the given row key. If the 664 * row key isn't in {@link #rowKeySet()}, an empty immutable map is 665 * returned. 666 * 667 * <p>Otherwise, for each column key in {@link #columnKeySet()}, the returned 668 * map associates the column key with the corresponding value in the 669 * table. Changes to the returned map will update the underlying table, and 670 * vice versa. 671 * 672 * @param rowKey key of row to search for in the table 673 * @return the corresponding map from column keys to values 674 */ 675 @Override 676 public Map<C, V> row(R rowKey) { 677 checkNotNull(rowKey); 678 Integer rowIndex = rowKeyToIndex.get(rowKey); 679 return (rowIndex == null) ? ImmutableMap.<C, V>of() : new Row(rowIndex); 680 } 681 682 private class Row extends AbstractMap<C, V> { 683 final int rowIndex; 684 685 Row(int rowIndex) { 686 this.rowIndex = rowIndex; 687 } 688 689 RowEntrySet entrySet; 690 691 @Override public Set<Entry<C, V>> entrySet() { 692 RowEntrySet set = entrySet; 693 return (set == null) ? entrySet = new RowEntrySet(rowIndex) : set; 694 } 695 696 @Override public V get(Object columnKey) { 697 Integer columnIndex = columnKeyToIndex.get(columnKey); 698 return getIndexed(rowIndex, columnIndex); 699 } 700 701 @Override public boolean containsKey(Object columnKey) { 702 return containsColumn(columnKey); 703 } 704 705 @Override public V put(C columnKey, V value) { 706 checkNotNull(columnKey); 707 Integer columnIndex = columnKeyToIndex.get(columnKey); 708 checkArgument(columnIndex != null, 709 "Column %s not in %s", columnKey, columnList); 710 return set(rowIndex, columnIndex, value); 711 } 712 713 @Override public Set<C> keySet() { 714 return columnKeySet(); 715 } 716 } 717 718 private class RowEntrySet extends AbstractSet<Entry<C, V>> { 719 final int rowIndex; 720 721 RowEntrySet(int rowIndex) { 722 this.rowIndex = rowIndex; 723 } 724 725 @Override public Iterator<Entry<C, V>> iterator() { 726 return new AbstractIndexedListIterator<Entry<C, V>>(size()) { 727 @Override protected Entry<C, V> get(final int columnIndex) { 728 return new AbstractMapEntry<C, V>() { 729 @Override public C getKey() { 730 return columnList.get(columnIndex); 731 } 732 @Override public V getValue() { 733 return array[rowIndex][columnIndex]; 734 } 735 @Override public V setValue(V value) { 736 return ArrayTable.this.set(rowIndex, columnIndex, value); 737 } 738 }; 739 } 740 }; 741 } 742 743 @Override public int size() { 744 return columnList.size(); 745 } 746 } 747 748 /** 749 * Returns an immutable set of the valid row keys, including those that are 750 * associated with null values only. 751 * 752 * @return immutable set of row keys 753 */ 754 @Override 755 public ImmutableSet<R> rowKeySet() { 756 return rowKeyToIndex.keySet(); 757 } 758 759 private transient RowMap rowMap; 760 761 @Override 762 public Map<R, Map<C, V>> rowMap() { 763 RowMap map = rowMap; 764 return (map == null) ? rowMap = new RowMap() : map; 765 } 766 767 private class RowMap extends AbstractMap<R, Map<C, V>> { 768 transient RowMapEntrySet entrySet; 769 770 @Override public Set<Entry<R, Map<C, V>>> entrySet() { 771 RowMapEntrySet set = entrySet; 772 return (set == null) ? entrySet = new RowMapEntrySet() : set; 773 } 774 775 @Override public Map<C, V> get(Object rowKey) { 776 Integer rowIndex = rowKeyToIndex.get(rowKey); 777 return (rowIndex == null) ? null : new Row(rowIndex); 778 } 779 780 @Override public boolean containsKey(Object rowKey) { 781 return containsRow(rowKey); 782 } 783 784 @Override public Set<R> keySet() { 785 return rowKeySet(); 786 } 787 788 @Override public Map<C, V> remove(Object rowKey) { 789 throw new UnsupportedOperationException(); 790 } 791 } 792 793 private class RowMapEntrySet extends AbstractSet<Entry<R, Map<C, V>>> { 794 @Override public Iterator<Entry<R, Map<C, V>>> iterator() { 795 return new AbstractIndexedListIterator<Entry<R, Map<C, V>>>(size()) { 796 @Override protected Entry<R, Map<C, V>> get(int index) { 797 return Maps.<R, Map<C, V>>immutableEntry(rowList.get(index), 798 new Row(index)); 799 } 800 }; 801 } 802 803 @Override public int size() { 804 return rowList.size(); 805 } 806 } 807 808 private transient Collection<V> values; 809 810 /** 811 * Returns an unmodifiable collection of all values, which may contain 812 * duplicates. Changes to the table will update the returned collection. 813 * 814 * <p>The returned collection's iterator traverses the values of the first row 815 * key, the values of the second row key, and so on. 816 * 817 * @return collection of values 818 */ 819 @Override 820 public Collection<V> values() { 821 Collection<V> v = values; 822 return (v == null) ? values = new Values() : v; 823 } 824 825 private class Values extends AbstractCollection<V> { 826 @Override public Iterator<V> iterator() { 827 return new AbstractIndexedListIterator<V>(size()) { 828 @Override protected V get(int index) { 829 int rowIndex = index / columnList.size(); 830 int columnIndex = index % columnList.size(); 831 return array[rowIndex][columnIndex]; 832 } 833 }; 834 } 835 836 @Override public int size() { 837 return ArrayTable.this.size(); 838 } 839 840 @Override public boolean contains(Object value) { 841 return containsValue(value); 842 } 843 } 844 845 private static final long serialVersionUID = 0; 846 }