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
017package com.google.common.collect;
018
019import static com.google.common.base.Preconditions.checkArgument;
020import static com.google.common.base.Preconditions.checkNotNull;
021import static com.google.common.collect.Iterables.transform;
022import static com.google.common.collect.Iterators.mergeSorted;
023import static java.util.Objects.requireNonNull;
024
025import com.google.common.annotations.GwtCompatible;
026import com.google.common.annotations.GwtIncompatible;
027import com.google.common.annotations.J2ktIncompatible;
028import com.google.common.base.Supplier;
029import com.google.errorprone.annotations.InlineMe;
030import java.io.Serializable;
031import java.util.Comparator;
032import java.util.Iterator;
033import java.util.Map;
034import java.util.NoSuchElementException;
035import java.util.Set;
036import java.util.SortedMap;
037import java.util.SortedSet;
038import java.util.TreeMap;
039import org.jspecify.annotations.Nullable;
040
041/**
042 * Implementation of {@code Table} whose row keys and column keys are ordered by their natural
043 * ordering or by supplied comparators. When constructing a {@code TreeBasedTable}, you may provide
044 * comparators for the row keys and the column keys, or you may use natural ordering for both.
045 *
046 * <p>The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link #rowMap} method
047 * returns a {@link SortedMap}, instead of the {@link Set} and {@link Map} specified by the {@link
048 * Table} interface.
049 *
050 * <p>The views returned by {@link #column}, {@link #columnKeySet()}, and {@link #columnMap()} have
051 * iterators that don't support {@code remove()}. Otherwise, all optional operations are supported.
052 * Null row keys, columns keys, and values are not supported.
053 *
054 * <p>Lookups by row key are often faster than lookups by column key, because the data is stored in
055 * a {@code Map<R, Map<C, V>>}. A method call like {@code column(columnKey).get(rowKey)} still runs
056 * quickly, since the row key is provided. However, {@code column(columnKey).size()} takes longer,
057 * since an iteration across all row keys occurs.
058 *
059 * <p>Because a {@code TreeBasedTable} has unique sorted values for a given row, both {@code
060 * row(rowKey)} and {@code rowMap().get(rowKey)} are {@link SortedMap} instances, instead of the
061 * {@link Map} specified in the {@link Table} interface.
062 *
063 * <p>Note that this implementation is not synchronized. If multiple threads access this table
064 * concurrently and one of the threads modifies the table, it must be synchronized externally.
065 *
066 * <p>See the Guava User Guide article on <a href=
067 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#table">{@code Table}</a>.
068 *
069 * @author Jared Levy
070 * @author Louis Wasserman
071 * @since 7.0
072 */
073@GwtCompatible(serializable = true)
074public class TreeBasedTable<R, C, V> extends StandardRowSortedTable<R, C, V> {
075  private final Comparator<? super C> columnComparator;
076
077  private static class Factory<C, V> implements Supplier<Map<C, V>>, Serializable {
078    final Comparator<? super C> comparator;
079
080    Factory(Comparator<? super C> comparator) {
081      this.comparator = comparator;
082    }
083
084    @Override
085    public Map<C, V> get() {
086      return new TreeMap<>(comparator);
087    }
088
089    @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0;
090  }
091
092  /**
093   * Creates an empty {@code TreeBasedTable} that uses the natural orderings of both row and column
094   * keys.
095   *
096   * <p>The method signature specifies {@code R extends Comparable} with a raw {@link Comparable},
097   * instead of {@code R extends Comparable<? super R>}, and the same for {@code C}. That's
098   * necessary to support classes defined without generics.
099   */
100  @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989
101  public static <R extends Comparable, C extends Comparable, V> TreeBasedTable<R, C, V> create() {
102    return new TreeBasedTable<>(Ordering.natural(), Ordering.natural());
103  }
104
105  /**
106   * Creates an empty {@code TreeBasedTable} that is ordered by the specified comparators.
107   *
108   * @param rowComparator the comparator that orders the row keys
109   * @param columnComparator the comparator that orders the column keys
110   */
111  public static <R, C, V> TreeBasedTable<R, C, V> create(
112      Comparator<? super R> rowComparator, Comparator<? super C> columnComparator) {
113    checkNotNull(rowComparator);
114    checkNotNull(columnComparator);
115    return new TreeBasedTable<>(rowComparator, columnComparator);
116  }
117
118  /**
119   * Creates a {@code TreeBasedTable} with the same mappings and sort order as the specified {@code
120   * TreeBasedTable}.
121   */
122  public static <R, C, V> TreeBasedTable<R, C, V> create(TreeBasedTable<R, C, ? extends V> table) {
123    TreeBasedTable<R, C, V> result =
124        // requireNonNull is safe, as discussed in rowComparator() below.
125        new TreeBasedTable<>(
126            requireNonNull(table.rowKeySet().comparator()), table.columnComparator());
127    result.putAll(table);
128    return result;
129  }
130
131  TreeBasedTable(Comparator<? super R> rowComparator, Comparator<? super C> columnComparator) {
132    super(new TreeMap<R, Map<C, V>>(rowComparator), new Factory<C, V>(columnComparator));
133    this.columnComparator = columnComparator;
134  }
135
136  // TODO(jlevy): Move to StandardRowSortedTable?
137
138  /**
139   * Returns the comparator that orders the rows. With natural ordering, {@link Ordering#natural()}
140   * is returned.
141   *
142   * @deprecated Use {@code table.rowKeySet().comparator()} instead.
143   */
144  @InlineMe(
145      replacement = "requireNonNull(this.rowKeySet().comparator())",
146      staticImports = "java.util.Objects.requireNonNull")
147  @Deprecated
148  public final Comparator<? super R> rowComparator() {
149    /*
150     * requireNonNull is safe because the factories require non-null Comparators, which they pass on
151     * to the backing collections.
152     */
153    return requireNonNull(rowKeySet().comparator());
154  }
155
156  /**
157   * Returns the comparator that orders the columns. With natural ordering, {@link
158   * Ordering#natural()} is returned.
159   *
160   * @deprecated Store the {@link Comparator} alongside the {@link Table}. Or, if you know that the
161   *     {@link Table} contains at least one value, you can retrieve the {@link Comparator} with:
162   *     {@code ((SortedMap<C, V>) table.rowMap().values().iterator().next()).comparator();}.
163   */
164  @Deprecated
165  public Comparator<? super C> columnComparator() {
166    return columnComparator;
167  }
168
169  // TODO(lowasser): make column return a SortedMap
170
171  /**
172   * {@inheritDoc}
173   *
174   * <p>Because a {@code TreeBasedTable} has unique sorted values for a given row, this method
175   * returns a {@link SortedMap}, instead of the {@link Map} specified in the {@link Table}
176   * interface.
177   *
178   * @since 10.0 (<a href="https://github.com/google/guava/wiki/Compatibility" >mostly
179   *     source-compatible</a> since 7.0)
180   */
181  @Override
182  public SortedMap<C, V> row(R rowKey) {
183    return new TreeRow(rowKey);
184  }
185
186  private class TreeRow extends Row implements SortedMap<C, V> {
187    final @Nullable C lowerBound;
188    final @Nullable C upperBound;
189
190    TreeRow(R rowKey) {
191      this(rowKey, null, null);
192    }
193
194    TreeRow(R rowKey, @Nullable C lowerBound, @Nullable C upperBound) {
195      super(rowKey);
196      this.lowerBound = lowerBound;
197      this.upperBound = upperBound;
198      checkArgument(
199          lowerBound == null || upperBound == null || compare(lowerBound, upperBound) <= 0);
200    }
201
202    @Override
203    public SortedSet<C> keySet() {
204      return new Maps.SortedKeySet<>(this);
205    }
206
207    @Override
208    public Comparator<? super C> comparator() {
209      return columnComparator();
210    }
211
212    int compare(Object a, Object b) {
213      // pretend we can compare anything
214      @SuppressWarnings("unchecked")
215      Comparator<Object> cmp = (Comparator<Object>) comparator();
216      return cmp.compare(a, b);
217    }
218
219    boolean rangeContains(@Nullable Object o) {
220      return o != null
221          && (lowerBound == null || compare(lowerBound, o) <= 0)
222          && (upperBound == null || compare(upperBound, o) > 0);
223    }
224
225    @Override
226    public SortedMap<C, V> subMap(C fromKey, C toKey) {
227      checkArgument(rangeContains(checkNotNull(fromKey)) && rangeContains(checkNotNull(toKey)));
228      return new TreeRow(rowKey, fromKey, toKey);
229    }
230
231    @Override
232    public SortedMap<C, V> headMap(C toKey) {
233      checkArgument(rangeContains(checkNotNull(toKey)));
234      return new TreeRow(rowKey, lowerBound, toKey);
235    }
236
237    @Override
238    public SortedMap<C, V> tailMap(C fromKey) {
239      checkArgument(rangeContains(checkNotNull(fromKey)));
240      return new TreeRow(rowKey, fromKey, upperBound);
241    }
242
243    @Override
244    public C firstKey() {
245      updateBackingRowMapField();
246      if (backingRowMap == null) {
247        throw new NoSuchElementException();
248      }
249      return ((SortedMap<C, V>) backingRowMap).firstKey();
250    }
251
252    @Override
253    public C lastKey() {
254      updateBackingRowMapField();
255      if (backingRowMap == null) {
256        throw new NoSuchElementException();
257      }
258      return ((SortedMap<C, V>) backingRowMap).lastKey();
259    }
260
261    transient @Nullable SortedMap<C, V> wholeRow;
262
263    // If the row was previously empty, we check if there's a new row here every time we're queried.
264    void updateWholeRowField() {
265      if (wholeRow == null || (wholeRow.isEmpty() && backingMap.containsKey(rowKey))) {
266        wholeRow = (SortedMap<C, V>) backingMap.get(rowKey);
267      }
268    }
269
270    @Override
271    @Nullable SortedMap<C, V> computeBackingRowMap() {
272      updateWholeRowField();
273      SortedMap<C, V> map = wholeRow;
274      if (map != null) {
275        if (lowerBound != null) {
276          map = map.tailMap(lowerBound);
277        }
278        if (upperBound != null) {
279          map = map.headMap(upperBound);
280        }
281        return map;
282      }
283      return null;
284    }
285
286    @Override
287    void maintainEmptyInvariant() {
288      updateWholeRowField();
289      if (wholeRow != null && wholeRow.isEmpty()) {
290        backingMap.remove(rowKey);
291        wholeRow = null;
292        backingRowMap = null;
293      }
294    }
295
296    @Override
297    public boolean containsKey(@Nullable Object key) {
298      return rangeContains(key) && super.containsKey(key);
299    }
300
301    @Override
302    public @Nullable V put(C key, V value) {
303      checkArgument(rangeContains(checkNotNull(key)));
304      return super.put(key, value);
305    }
306  }
307
308  /** Overridden column iterator to return columns values in globally sorted order. */
309  @Override
310  Iterator<C> createColumnKeyIterator() {
311    Comparator<? super C> comparator = columnComparator();
312
313    Iterator<C> merged =
314        mergeSorted(
315            transform(backingMap.values(), (Map<C, V> input) -> input.keySet().iterator()),
316            comparator);
317
318    return new AbstractIterator<C>() {
319      @Nullable C lastValue;
320
321      @Override
322      protected @Nullable C computeNext() {
323        while (merged.hasNext()) {
324          C next = merged.next();
325          boolean duplicate = lastValue != null && comparator.compare(next, lastValue) == 0;
326
327          // Keep looping till we find a non-duplicate value.
328          if (!duplicate) {
329            lastValue = next;
330            return lastValue;
331          }
332        }
333
334        lastValue = null; // clear reference to unused data
335        return endOfData();
336      }
337    };
338  }
339
340  @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0;
341}