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 }