/*
 * Copyright (C) 2009 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.Nullable;

An immutable sorted set with one or more elements. TODO(jlevy): Consider separate class for a single-element sorted set.
Author:Jared Levy, Louis Wasserman
/** * An immutable sorted set with one or more elements. TODO(jlevy): Consider separate class for a * single-element sorted set. * * @author Jared Levy * @author Louis Wasserman */
@GwtCompatible(serializable = true, emulated = true) @SuppressWarnings("serial") final class RegularImmutableSortedSet<E> extends ImmutableSortedSet<E> { static final RegularImmutableSortedSet<Comparable> NATURAL_EMPTY_SET = new RegularImmutableSortedSet<>(ImmutableList.<Comparable>of(), Ordering.natural()); private final transient ImmutableList<E> elements; RegularImmutableSortedSet(ImmutableList<E> elements, Comparator<? super E> comparator) { super(comparator); this.elements = elements; } @Override public UnmodifiableIterator<E> iterator() { return elements.iterator(); } @GwtIncompatible // NavigableSet @Override public UnmodifiableIterator<E> descendingIterator() { return elements.reverse().iterator(); } @Override public Spliterator<E> spliterator() { return asList().spliterator(); } @Override public void forEach(Consumer<? super E> action) { elements.forEach(action); } @Override public int size() { return elements.size(); } @Override public boolean contains(@Nullable Object o) { try { return o != null && unsafeBinarySearch(o) >= 0; } catch (ClassCastException e) { return false; } } @Override public boolean containsAll(Collection<?> targets) { // TODO(jlevy): For optimal performance, use a binary search when // targets.size() < size() / log(size()) // TODO(kevinb): see if we can share code with OrderedIterator after it // graduates from labs. if (targets instanceof Multiset) { targets = ((Multiset<?>) targets).elementSet(); } if (!SortedIterables.hasSameComparator(comparator(), targets) || (targets.size() <= 1)) { return super.containsAll(targets); } /* * If targets is a sorted set with the same comparator, containsAll can run * in O(n) time stepping through the two collections. */ Iterator<E> thisIterator = iterator(); Iterator<?> thatIterator = targets.iterator(); // known nonempty since we checked targets.size() > 1 if (!thisIterator.hasNext()) { return false; } Object target = thatIterator.next(); E current = thisIterator.next(); try { while (true) { int cmp = unsafeCompare(current, target); if (cmp < 0) { if (!thisIterator.hasNext()) { return false; } current = thisIterator.next(); } else if (cmp == 0) { if (!thatIterator.hasNext()) { return true; } target = thatIterator.next(); } else if (cmp > 0) { return false; } } } catch (NullPointerException | ClassCastException e) { return false; } } private int unsafeBinarySearch(Object key) throws ClassCastException { return Collections.binarySearch(elements, key, unsafeComparator()); } @Override boolean isPartialView() { return elements.isPartialView(); } @Override int copyIntoArray(Object[] dst, int offset) { return elements.copyIntoArray(dst, offset); } @Override public boolean equals(@Nullable Object object) { if (object == this) { return true; } if (!(object instanceof Set)) { return false; } Set<?> that = (Set<?>) object; if (size() != that.size()) { return false; } else if (isEmpty()) { return true; } if (SortedIterables.hasSameComparator(comparator, that)) { Iterator<?> otherIterator = that.iterator(); try { Iterator<E> iterator = iterator(); while (iterator.hasNext()) { Object element = iterator.next(); Object otherElement = otherIterator.next(); if (otherElement == null || unsafeCompare(element, otherElement) != 0) { return false; } } return true; } catch (ClassCastException e) { return false; } catch (NoSuchElementException e) { return false; // concurrent change to other set } } return containsAll(that); } @Override public E first() { if (isEmpty()) { throw new NoSuchElementException(); } return elements.get(0); } @Override public E last() { if (isEmpty()) { throw new NoSuchElementException(); } return elements.get(size() - 1); } @Override public E lower(E element) { int index = headIndex(element, false) - 1; return (index == -1) ? null : elements.get(index); } @Override public E floor(E element) { int index = headIndex(element, true) - 1; return (index == -1) ? null : elements.get(index); } @Override public E ceiling(E element) { int index = tailIndex(element, true); return (index == size()) ? null : elements.get(index); } @Override public E higher(E element) { int index = tailIndex(element, false); return (index == size()) ? null : elements.get(index); } @Override ImmutableSortedSet<E> headSetImpl(E toElement, boolean inclusive) { return getSubSet(0, headIndex(toElement, inclusive)); } int headIndex(E toElement, boolean inclusive) { int index = Collections.binarySearch(elements, checkNotNull(toElement), comparator()); if (index >= 0) { return inclusive ? index + 1 : index; } else { return ~index; } } @Override ImmutableSortedSet<E> subSetImpl( E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { return tailSetImpl(fromElement, fromInclusive).headSetImpl(toElement, toInclusive); } @Override ImmutableSortedSet<E> tailSetImpl(E fromElement, boolean inclusive) { return getSubSet(tailIndex(fromElement, inclusive), size()); } int tailIndex(E fromElement, boolean inclusive) { int index = Collections.binarySearch(elements, checkNotNull(fromElement), comparator()); if (index >= 0) { return inclusive ? index : index + 1; } else { return ~index; } } // Pretend the comparator can compare anything. If it turns out it can't // compare two elements, it'll throw a CCE. Only methods that are specified to // throw CCE should call this. @SuppressWarnings("unchecked") Comparator<Object> unsafeComparator() { return (Comparator<Object>) comparator; } RegularImmutableSortedSet<E> getSubSet(int newFromIndex, int newToIndex) { if (newFromIndex == 0 && newToIndex == size()) { return this; } else if (newFromIndex < newToIndex) { return new RegularImmutableSortedSet<E>( elements.subList(newFromIndex, newToIndex), comparator); } else { return emptySet(comparator); } } @Override int indexOf(@Nullable Object target) { if (target == null) { return -1; } int position; try { position = Collections.binarySearch(elements, target, unsafeComparator()); } catch (ClassCastException e) { return -1; } return (position >= 0) ? position : -1; } @Override ImmutableList<E> createAsList() { return (size() <= 1) ? elements : new ImmutableSortedAsList<E>(this, elements); } @Override ImmutableSortedSet<E> createDescendingSet() { Comparator<? super E> reversedOrder = Collections.reverseOrder(comparator); return isEmpty() ? emptySet(reversedOrder) : new RegularImmutableSortedSet<E>(elements.reverse(), reversedOrder); } }