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.checkNotNull;
020
021 import com.google.common.annotations.Beta;
022 import com.google.common.annotations.GwtCompatible;
023 import com.google.common.base.Function;
024 import com.google.common.base.Objects;
025 import com.google.common.collect.Collections2.TransformedCollection;
026 import com.google.common.collect.Table.Cell;
027
028 import java.io.Serializable;
029 import java.util.Collection;
030 import java.util.Map;
031 import java.util.Set;
032
033 import javax.annotation.Nullable;
034
035 /**
036 * Provides static methods that involve a {@code Table}.
037 *
038 * @author Jared Levy
039 * @since 7
040 */
041 @GwtCompatible
042 @Beta
043 public final class Tables {
044 private Tables() {}
045
046 /**
047 * Returns an immutable cell with the specified row key, column key, and
048 * value.
049 *
050 * <p>The returned cell is serializable.
051 *
052 * @param rowKey the row key to be associated with the returned cell
053 * @param columnKey the column key to be associated with the returned cell
054 * @param value the value to be associated with the returned cell
055 */
056 public static <R, C, V> Cell<R, C, V> immutableCell(
057 @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) {
058 return new ImmutableCell<R, C, V>(rowKey, columnKey, value);
059 }
060
061 private static class ImmutableCell<R, C, V>
062 extends AbstractCell<R, C, V> implements Serializable {
063 final R rowKey;
064 final C columnKey;
065 final V value;
066
067 ImmutableCell(
068 @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) {
069 this.rowKey = rowKey;
070 this.columnKey = columnKey;
071 this.value = value;
072 }
073
074 @Override
075 public R getRowKey() {
076 return rowKey;
077 }
078 @Override
079 public C getColumnKey() {
080 return columnKey;
081 }
082 @Override
083 public V getValue() {
084 return value;
085 }
086
087 private static final long serialVersionUID = 0;
088 }
089
090 abstract static class AbstractCell<R, C, V> implements Cell<R, C, V> {
091 // needed for serialization
092 AbstractCell() {}
093
094 @Override public boolean equals(Object obj) {
095 if (obj == this) {
096 return true;
097 }
098 if (obj instanceof Cell) {
099 Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj;
100 return Objects.equal(getRowKey(), other.getRowKey())
101 && Objects.equal(getColumnKey(), other.getColumnKey())
102 && Objects.equal(getValue(), other.getValue());
103 }
104 return false;
105 }
106
107 @Override public int hashCode() {
108 return Objects.hashCode(getRowKey(), getColumnKey(), getValue());
109 }
110
111 @Override public String toString() {
112 return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue();
113 }
114 }
115
116 /**
117 * Creates a transposed view of a given table that flips its row and column
118 * keys. In other words, calling {@code get(columnKey, rowKey)} on the
119 * generated table always returns the same value as calling {@code
120 * get(rowKey, columnKey)} on the original table. Updating the original table
121 * changes the contents of the transposed table and vice versa.
122 *
123 * <p>The returned table supports update operations as long as the input table
124 * supports the analogous operation with swapped rows and columns. For
125 * example, in a {@link HashBasedTable} instance, {@code
126 * rowKeySet().iterator()} supports {@code remove()} but {@code
127 * columnKeySet().iterator()} doesn't. With a transposed {@link
128 * HashBasedTable}, it's the other way around.
129 */
130 public static <R, C, V> Table<C, R, V> transpose(Table<R, C, V> table) {
131 return (table instanceof TransposeTable)
132 ? ((TransposeTable<R, C, V>) table).original
133 : new TransposeTable<C, R, V>(table);
134 }
135
136 private static class TransposeTable<C, R, V> implements Table<C, R, V> {
137 final Table<R, C, V> original;
138
139 TransposeTable(Table<R, C, V> original) {
140 this.original = checkNotNull(original);
141 }
142
143 @Override
144 public void clear() {
145 original.clear();
146 }
147
148 @Override
149 public Map<C, V> column(R columnKey) {
150 return original.row(columnKey);
151 }
152
153 @Override
154 public Set<R> columnKeySet() {
155 return original.rowKeySet();
156 }
157
158 @Override
159 public Map<R, Map<C, V>> columnMap() {
160 return original.rowMap();
161 }
162
163 @Override
164 public boolean contains(
165 @Nullable Object rowKey, @Nullable Object columnKey) {
166 return original.contains(columnKey, rowKey);
167 }
168
169 @Override
170 public boolean containsColumn(@Nullable Object columnKey) {
171 return original.containsRow(columnKey);
172 }
173
174 @Override
175 public boolean containsRow(@Nullable Object rowKey) {
176 return original.containsColumn(rowKey);
177 }
178
179 @Override
180 public boolean containsValue(@Nullable Object value) {
181 return original.containsValue(value);
182 }
183
184 @Override
185 public V get(@Nullable Object rowKey, @Nullable Object columnKey) {
186 return original.get(columnKey, rowKey);
187 }
188
189 @Override
190 public boolean isEmpty() {
191 return original.isEmpty();
192 }
193
194 @Override
195 public V put(C rowKey, R columnKey, V value) {
196 return original.put(columnKey, rowKey, value);
197 }
198
199 @Override
200 public void putAll(Table<? extends C, ? extends R, ? extends V> table) {
201 original.putAll(transpose(table));
202 }
203
204 @Override
205 public V remove(@Nullable Object rowKey, @Nullable Object columnKey) {
206 return original.remove(columnKey, rowKey);
207 }
208
209 @Override
210 public Map<R, V> row(C rowKey) {
211 return original.column(rowKey);
212 }
213
214 @Override
215 public Set<C> rowKeySet() {
216 return original.columnKeySet();
217 }
218
219 @Override
220 public Map<C, Map<R, V>> rowMap() {
221 return original.columnMap();
222 }
223
224 @Override
225 public int size() {
226 return original.size();
227 }
228
229 @Override
230 public Collection<V> values() {
231 return original.values();
232 }
233
234 @Override public boolean equals(@Nullable Object obj) {
235 if (obj == this) {
236 return true;
237 }
238 if (obj instanceof Table) {
239 Table<?, ?, ?> other = (Table<?, ?, ?>) obj;
240 return cellSet().equals(other.cellSet());
241 }
242 return false;
243 }
244
245 @Override public int hashCode() {
246 return cellSet().hashCode();
247 }
248
249 @Override public String toString() {
250 return rowMap().toString();
251 }
252
253 // Will cast TRANSPOSE_CELL to a type that always succeeds
254 @SuppressWarnings("unchecked") // eclipse doesn't like the raw type
255 private static final Function<Cell<?, ?, ?>, Cell<?, ?, ?>> TRANSPOSE_CELL =
256 new Function<Cell<?, ?, ?>, Cell<?, ?, ?>>() {
257 @Override
258 public Cell<?, ?, ?> apply(Cell<?, ?, ?> cell) {
259 return immutableCell(
260 cell.getColumnKey(), cell.getRowKey(), cell.getValue());
261 }
262 };
263
264 CellSet cellSet;
265
266 @Override
267 public Set<Cell<C, R, V>> cellSet() {
268 CellSet result = cellSet;
269 return (result == null) ? cellSet = new CellSet() : result;
270 }
271
272 class CellSet extends TransformedCollection<Cell<R, C, V>, Cell<C, R, V>>
273 implements Set<Cell<C, R, V>> {
274 // Casting TRANSPOSE_CELL to a type that always succeeds
275 @SuppressWarnings("unchecked")
276 CellSet() {
277 super(original.cellSet(), (Function) TRANSPOSE_CELL);
278 }
279
280 @Override public boolean equals(Object obj) {
281 if (obj == this) {
282 return true;
283 }
284 if (!(obj instanceof Set)) {
285 return false;
286 }
287 Set<?> os = (Set<?>) obj;
288 if (os.size() != size()) {
289 return false;
290 }
291 return containsAll(os);
292 }
293
294 @Override public int hashCode() {
295 return Sets.hashCodeImpl(this);
296 }
297
298 @Override public boolean contains(Object obj) {
299 if (obj instanceof Cell) {
300 Cell<?, ?, ?> cell = (Cell<?, ?, ?>) obj;
301 return original.cellSet().contains(immutableCell(
302 cell.getColumnKey(), cell.getRowKey(), cell.getValue()));
303 }
304 return false;
305 }
306
307 @Override public boolean remove(Object obj) {
308 if (obj instanceof Cell) {
309 Cell<?, ?, ?> cell = (Cell<?, ?, ?>) obj;
310 return original.cellSet().remove(immutableCell(
311 cell.getColumnKey(), cell.getRowKey(), cell.getValue()));
312 }
313 return false;
314 }
315 }
316 }
317 }