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    }