001 /*
002 * Copyright (C) 2008 Google Inc.
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 public R getRowKey() {
075 return rowKey;
076 }
077 public C getColumnKey() {
078 return columnKey;
079 }
080 public V getValue() {
081 return value;
082 }
083
084 private static final long serialVersionUID = 0;
085 }
086
087 abstract static class AbstractCell<R, C, V> implements Cell<R, C, V> {
088 // needed for serialization
089 AbstractCell() {}
090
091 @Override public boolean equals(Object obj) {
092 if (obj == this) {
093 return true;
094 }
095 if (obj instanceof Cell) {
096 Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj;
097 return Objects.equal(getRowKey(), other.getRowKey())
098 && Objects.equal(getColumnKey(), other.getColumnKey())
099 && Objects.equal(getValue(), other.getValue());
100 }
101 return false;
102 }
103
104 @Override public int hashCode() {
105 return Objects.hashCode(getRowKey(), getColumnKey(), getValue());
106 }
107
108 @Override public String toString() {
109 return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue();
110 }
111 }
112
113 /**
114 * Creates a transposed view of a given table that flips its row and column
115 * keys. In other words, calling {@code get(columnKey, rowKey)} on the
116 * generated table always returns the same value as calling {@code
117 * get(rowKey, columnKey)} on the original table. Updating the original table
118 * changes the contents of the transposed table and vice versa.
119 *
120 * <p>The returned table supports update operations as long as the input table
121 * supports the analogous operation with swapped rows and columns. For
122 * example, in a {@link HashBasedTable} instance, {@code
123 * rowKeySet().iterator()} supports {@code remove()} but {@code
124 * columnKeySet().iterator()} doesn't. With a transposed {@link
125 * HashBasedTable}, it's the other way around.
126 */
127 public static <R, C, V> Table<C, R, V> transpose(Table<R, C, V> table) {
128 return (table instanceof TransposeTable)
129 ? ((TransposeTable<R, C, V>) table).original
130 : new TransposeTable<C, R, V>(table);
131 }
132
133 private static class TransposeTable<C, R, V> implements Table<C, R, V> {
134 final Table<R, C, V> original;
135
136 TransposeTable(Table<R, C, V> original) {
137 this.original = checkNotNull(original);
138 }
139
140 public void clear() {
141 original.clear();
142 }
143
144 public Map<C, V> column(R columnKey) {
145 return original.row(columnKey);
146 }
147
148 public Set<R> columnKeySet() {
149 return original.rowKeySet();
150 }
151
152 public Map<R, Map<C, V>> columnMap() {
153 return original.rowMap();
154 }
155
156 public boolean contains(
157 @Nullable Object rowKey, @Nullable Object columnKey) {
158 return original.contains(columnKey, rowKey);
159 }
160
161 public boolean containsColumn(@Nullable Object columnKey) {
162 return original.containsRow(columnKey);
163 }
164
165 public boolean containsRow(@Nullable Object rowKey) {
166 return original.containsColumn(rowKey);
167 }
168
169 public boolean containsValue(@Nullable Object value) {
170 return original.containsValue(value);
171 }
172
173 public V get(@Nullable Object rowKey, @Nullable Object columnKey) {
174 return original.get(columnKey, rowKey);
175 }
176
177 public boolean isEmpty() {
178 return original.isEmpty();
179 }
180
181 public V put(C rowKey, R columnKey, V value) {
182 return original.put(columnKey, rowKey, value);
183 }
184
185 public void putAll(Table<? extends C, ? extends R, ? extends V> table) {
186 original.putAll(transpose(table));
187 }
188
189 public V remove(@Nullable Object rowKey, @Nullable Object columnKey) {
190 return original.remove(columnKey, rowKey);
191 }
192
193 public Map<R, V> row(C rowKey) {
194 return original.column(rowKey);
195 }
196
197 public Set<C> rowKeySet() {
198 return original.columnKeySet();
199 }
200
201 public Map<C, Map<R, V>> rowMap() {
202 return original.columnMap();
203 }
204
205 public int size() {
206 return original.size();
207 }
208
209 public Collection<V> values() {
210 return original.values();
211 }
212
213 @Override public boolean equals(@Nullable Object obj) {
214 if (obj == this) {
215 return true;
216 }
217 if (obj instanceof Table) {
218 Table<?, ?, ?> other = (Table<?, ?, ?>) obj;
219 return cellSet().equals(other.cellSet());
220 }
221 return false;
222 }
223
224 @Override public int hashCode() {
225 return cellSet().hashCode();
226 }
227
228 @Override public String toString() {
229 return rowMap().toString();
230 }
231
232 // Will cast TRANSPOSE_CELL to a type that always succeeds
233 @SuppressWarnings("unchecked") // eclipse doesn't like the raw type
234 private static final Function TRANSPOSE_CELL = new Function() {
235 public Object apply(Object from) {
236 Cell<?, ?, ?> cell = (Cell<?, ?, ?>) from;
237 return immutableCell(
238 cell.getColumnKey(), cell.getRowKey(), cell.getValue());
239 }
240 };
241
242 CellSet cellSet;
243
244 public Set<Cell<C, R, V>> cellSet() {
245 CellSet result = cellSet;
246 return (result == null) ? cellSet = new CellSet() : result;
247 }
248
249 class CellSet extends TransformedCollection<Cell<R, C, V>, Cell<C, R, V>>
250 implements Set<Cell<C, R, V>> {
251 // Casting TRANSPOSE_CELL to a type that always succeeds
252 @SuppressWarnings("unchecked")
253 CellSet() {
254 super(original.cellSet(), TRANSPOSE_CELL);
255 }
256
257 @Override public boolean equals(Object obj) {
258 if (obj == this) {
259 return true;
260 }
261 if (!(obj instanceof Set)) {
262 return false;
263 }
264 Set<?> os = (Set<?>) obj;
265 if (os.size() != size()) {
266 return false;
267 }
268 return containsAll(os);
269 }
270
271 @Override public int hashCode() {
272 return Sets.hashCodeImpl(this);
273 }
274
275 @Override public boolean contains(Object obj) {
276 if (obj instanceof Cell) {
277 Cell<?, ?, ?> cell = (Cell<?, ?, ?>) obj;
278 return original.cellSet().contains(immutableCell(
279 cell.getColumnKey(), cell.getRowKey(), cell.getValue()));
280 }
281 return false;
282 }
283
284 @Override public boolean remove(Object obj) {
285 if (obj instanceof Cell) {
286 Cell<?, ?, ?> cell = (Cell<?, ?, ?>) obj;
287 return original.cellSet().remove(immutableCell(
288 cell.getColumnKey(), cell.getRowKey(), cell.getValue()));
289 }
290 return false;
291 }
292 }
293 }
294 }