001/*
002 * Copyright (C) 2010 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.testing;
018
019import com.google.common.annotations.GwtIncompatible;
020import com.google.errorprone.annotations.CanIgnoreReturnValue;
021import java.io.Serializable;
022import java.util.AbstractSet;
023import java.util.Collection;
024import java.util.Comparator;
025import java.util.Iterator;
026import java.util.Map;
027import java.util.NavigableMap;
028import java.util.NavigableSet;
029import java.util.Set;
030import java.util.SortedMap;
031import java.util.TreeMap;
032import org.checkerframework.checker.nullness.qual.Nullable;
033
034/**
035 * A wrapper around {@code TreeMap} that aggressively checks to see if keys are mutually comparable.
036 * This implementation passes the navigable map test suites.
037 *
038 * @author Louis Wasserman
039 */
040@GwtIncompatible
041public final class SafeTreeMap<K, V> implements Serializable, NavigableMap<K, V> {
042  @SuppressWarnings("unchecked")
043  private static final Comparator<Object> NATURAL_ORDER =
044      new Comparator<Object>() {
045        @Override
046        public int compare(Object o1, Object o2) {
047          return ((Comparable<Object>) o1).compareTo(o2);
048        }
049      };
050
051  private final NavigableMap<K, V> delegate;
052
053  public SafeTreeMap() {
054    this(new TreeMap<K, V>());
055  }
056
057  public SafeTreeMap(Comparator<? super K> comparator) {
058    this(new TreeMap<K, V>(comparator));
059  }
060
061  public SafeTreeMap(Map<? extends K, ? extends V> map) {
062    this(new TreeMap<K, V>(map));
063  }
064
065  public SafeTreeMap(SortedMap<K, ? extends V> map) {
066    this(new TreeMap<K, V>(map));
067  }
068
069  private SafeTreeMap(NavigableMap<K, V> delegate) {
070    this.delegate = delegate;
071    if (delegate == null) {
072      throw new NullPointerException();
073    }
074    for (K k : keySet()) {
075      checkValid(k);
076    }
077  }
078
079  @Override
080  public @Nullable Entry<K, V> ceilingEntry(K key) {
081    return delegate.ceilingEntry(checkValid(key));
082  }
083
084  @Override
085  public @Nullable K ceilingKey(K key) {
086    return delegate.ceilingKey(checkValid(key));
087  }
088
089  @Override
090  public void clear() {
091    delegate.clear();
092  }
093
094  @SuppressWarnings("unchecked")
095  @Override
096  public Comparator<? super K> comparator() {
097    Comparator<? super K> comparator = delegate.comparator();
098    if (comparator == null) {
099      comparator = (Comparator<? super K>) NATURAL_ORDER;
100    }
101    return comparator;
102  }
103
104  @Override
105  public boolean containsKey(Object key) {
106    try {
107      return delegate.containsKey(checkValid(key));
108    } catch (NullPointerException | ClassCastException e) {
109      return false;
110    }
111  }
112
113  @Override
114  public boolean containsValue(Object value) {
115    return delegate.containsValue(value);
116  }
117
118  @Override
119  public NavigableSet<K> descendingKeySet() {
120    return delegate.descendingKeySet();
121  }
122
123  @Override
124  public NavigableMap<K, V> descendingMap() {
125    return new SafeTreeMap<>(delegate.descendingMap());
126  }
127
128  @Override
129  public Set<Entry<K, V>> entrySet() {
130    return new AbstractSet<Entry<K, V>>() {
131      private Set<Entry<K, V>> delegate() {
132        return delegate.entrySet();
133      }
134
135      @Override
136      public boolean contains(Object object) {
137        try {
138          return delegate().contains(object);
139        } catch (NullPointerException | ClassCastException e) {
140          return false;
141        }
142      }
143
144      @Override
145      public Iterator<Entry<K, V>> iterator() {
146        return delegate().iterator();
147      }
148
149      @Override
150      public int size() {
151        return delegate().size();
152      }
153
154      @Override
155      public boolean remove(Object o) {
156        return delegate().remove(o);
157      }
158
159      @Override
160      public void clear() {
161        delegate().clear();
162      }
163    };
164  }
165
166  @Override
167  public @Nullable Entry<K, V> firstEntry() {
168    return delegate.firstEntry();
169  }
170
171  @Override
172  public K firstKey() {
173    return delegate.firstKey();
174  }
175
176  @Override
177  public @Nullable Entry<K, V> floorEntry(K key) {
178    return delegate.floorEntry(checkValid(key));
179  }
180
181  @Override
182  public @Nullable K floorKey(K key) {
183    return delegate.floorKey(checkValid(key));
184  }
185
186  @Override
187  public @Nullable V get(Object key) {
188    return delegate.get(checkValid(key));
189  }
190
191  @Override
192  public SortedMap<K, V> headMap(K toKey) {
193    return headMap(toKey, false);
194  }
195
196  @Override
197  public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
198    return new SafeTreeMap<>(delegate.headMap(checkValid(toKey), inclusive));
199  }
200
201  @Override
202  public @Nullable Entry<K, V> higherEntry(K key) {
203    return delegate.higherEntry(checkValid(key));
204  }
205
206  @Override
207  public @Nullable K higherKey(K key) {
208    return delegate.higherKey(checkValid(key));
209  }
210
211  @Override
212  public boolean isEmpty() {
213    return delegate.isEmpty();
214  }
215
216  @Override
217  public NavigableSet<K> keySet() {
218    return navigableKeySet();
219  }
220
221  @Override
222  public @Nullable Entry<K, V> lastEntry() {
223    return delegate.lastEntry();
224  }
225
226  @Override
227  public K lastKey() {
228    return delegate.lastKey();
229  }
230
231  @Override
232  public @Nullable Entry<K, V> lowerEntry(K key) {
233    return delegate.lowerEntry(checkValid(key));
234  }
235
236  @Override
237  public @Nullable K lowerKey(K key) {
238    return delegate.lowerKey(checkValid(key));
239  }
240
241  @Override
242  public NavigableSet<K> navigableKeySet() {
243    return delegate.navigableKeySet();
244  }
245
246  @Override
247  public @Nullable Entry<K, V> pollFirstEntry() {
248    return delegate.pollFirstEntry();
249  }
250
251  @Override
252  public @Nullable Entry<K, V> pollLastEntry() {
253    return delegate.pollLastEntry();
254  }
255
256  @Override
257  public @Nullable V put(K key, V value) {
258    return delegate.put(checkValid(key), value);
259  }
260
261  @Override
262  public void putAll(Map<? extends K, ? extends V> map) {
263    for (K key : map.keySet()) {
264      checkValid(key);
265    }
266    delegate.putAll(map);
267  }
268
269  @Override
270  public @Nullable V remove(Object key) {
271    return delegate.remove(checkValid(key));
272  }
273
274  @Override
275  public int size() {
276    return delegate.size();
277  }
278
279  @Override
280  public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
281    return new SafeTreeMap<>(
282        delegate.subMap(checkValid(fromKey), fromInclusive, checkValid(toKey), toInclusive));
283  }
284
285  @Override
286  public SortedMap<K, V> subMap(K fromKey, K toKey) {
287    return subMap(fromKey, true, toKey, false);
288  }
289
290  @Override
291  public SortedMap<K, V> tailMap(K fromKey) {
292    return tailMap(fromKey, true);
293  }
294
295  @Override
296  public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
297    return new SafeTreeMap<>(delegate.tailMap(checkValid(fromKey), inclusive));
298  }
299
300  @Override
301  public Collection<V> values() {
302    return delegate.values();
303  }
304
305  @CanIgnoreReturnValue
306  private <T> T checkValid(T t) {
307    // a ClassCastException is what's supposed to happen!
308    @SuppressWarnings("unchecked")
309    K k = (K) t;
310    int unused = comparator().compare(k, k);
311    return t;
312  }
313
314  @Override
315  public boolean equals(@Nullable Object obj) {
316    return delegate.equals(obj);
317  }
318
319  @Override
320  public int hashCode() {
321    return delegate.hashCode();
322  }
323
324  @Override
325  public String toString() {
326    return delegate.toString();
327  }
328
329  private static final long serialVersionUID = 0L;
330}