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}