001/*
002 * Copyright (C) 2015 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 static com.google.common.base.Preconditions.checkNotNull;
020import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder;
021import static com.google.common.collect.testing.Helpers.assertEqualInOrder;
022import static com.google.common.collect.testing.Platform.format;
023import static java.util.Arrays.asList;
024import static java.util.Collections.unmodifiableSet;
025import static junit.framework.Assert.assertEquals;
026import static junit.framework.Assert.assertFalse;
027import static junit.framework.Assert.assertTrue;
028import static junit.framework.Assert.fail;
029
030import com.google.common.annotations.GwtCompatible;
031import com.google.common.collect.ImmutableSet;
032import com.google.common.collect.Ordering;
033import com.google.common.primitives.Ints;
034import com.google.errorprone.annotations.CanIgnoreReturnValue;
035import java.util.ArrayList;
036import java.util.Comparator;
037import java.util.LinkedHashSet;
038import java.util.List;
039import java.util.Set;
040import java.util.Spliterator;
041import java.util.Spliterator.OfPrimitive;
042import java.util.function.Consumer;
043import java.util.function.Function;
044import java.util.function.Supplier;
045import org.jspecify.annotations.NullMarked;
046import org.jspecify.annotations.Nullable;
047
048/**
049 * Tester for {@code Spliterator} implementations.
050 *
051 * @since 33.4.0 (but since 21.0 in the JRE flavor)
052 */
053@GwtCompatible
054@NullMarked
055@SuppressWarnings("Java7ApiChecker")
056@IgnoreJRERequirement // Users will use this only if they're already using Spliterator.
057public final class SpliteratorTester<E extends @Nullable Object> {
058  /** Return type from "contains the following elements" assertions. */
059  public interface Ordered {
060    /**
061     * Attests that the expected values must not just be present but must be present in the order
062     * they were given.
063     */
064    void inOrder();
065  }
066
067  @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester
068  private abstract static class GeneralSpliterator<E extends @Nullable Object> {
069    final Spliterator<E> spliterator;
070
071    GeneralSpliterator(Spliterator<E> spliterator) {
072      this.spliterator = checkNotNull(spliterator);
073    }
074
075    abstract void forEachRemaining(Consumer<? super E> action);
076
077    abstract boolean tryAdvance(Consumer<? super E> action);
078
079    abstract @Nullable GeneralSpliterator<E> trySplit();
080
081    final int characteristics() {
082      return spliterator.characteristics();
083    }
084
085    final long estimateSize() {
086      return spliterator.estimateSize();
087    }
088
089    final Comparator<? super E> getComparator() {
090      return spliterator.getComparator();
091    }
092
093    final long getExactSizeIfKnown() {
094      return spliterator.getExactSizeIfKnown();
095    }
096
097    final boolean hasCharacteristics(int characteristics) {
098      return spliterator.hasCharacteristics(characteristics);
099    }
100  }
101
102  @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester
103  private static final class GeneralSpliteratorOfObject<E extends @Nullable Object>
104      extends GeneralSpliterator<E> {
105    GeneralSpliteratorOfObject(Spliterator<E> spliterator) {
106      super(spliterator);
107    }
108
109    @Override
110    void forEachRemaining(Consumer<? super E> action) {
111      spliterator.forEachRemaining(action);
112    }
113
114    @Override
115    boolean tryAdvance(Consumer<? super E> action) {
116      return spliterator.tryAdvance(action);
117    }
118
119    @Override
120    @Nullable GeneralSpliterator<E> trySplit() {
121      Spliterator<E> split = spliterator.trySplit();
122      return split == null ? null : new GeneralSpliteratorOfObject<>(split);
123    }
124  }
125
126  @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester
127  private static final class GeneralSpliteratorOfPrimitive<
128          E extends @Nullable Object, C, S extends Spliterator.OfPrimitive<E, C, S>>
129      extends GeneralSpliterator<E> {
130    final OfPrimitive<E, C, S> spliteratorOfPrimitive;
131    final Function<Consumer<? super E>, C> consumerizer;
132
133    GeneralSpliteratorOfPrimitive(
134        Spliterator.OfPrimitive<E, C, S> spliterator,
135        Function<Consumer<? super E>, C> consumerizer) {
136      super(spliterator);
137      this.spliteratorOfPrimitive = spliterator;
138      this.consumerizer = consumerizer;
139    }
140
141    @Override
142    void forEachRemaining(Consumer<? super E> action) {
143      spliteratorOfPrimitive.forEachRemaining(consumerizer.apply(action));
144    }
145
146    @Override
147    boolean tryAdvance(Consumer<? super E> action) {
148      return spliteratorOfPrimitive.tryAdvance(consumerizer.apply(action));
149    }
150
151    @Override
152    @Nullable GeneralSpliterator<E> trySplit() {
153      Spliterator.OfPrimitive<E, C, ?> split = spliteratorOfPrimitive.trySplit();
154      return split == null ? null : new GeneralSpliteratorOfPrimitive<>(split, consumerizer);
155    }
156  }
157
158  /**
159   * Different ways of decomposing a Spliterator, all of which must produce the same elements (up to
160   * ordering, if Spliterator.ORDERED is not present).
161   */
162  @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester
163  enum SpliteratorDecompositionStrategy {
164    NO_SPLIT_FOR_EACH_REMAINING {
165      @Override
166      <E extends @Nullable Object> void forEach(
167          GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
168        spliterator.forEachRemaining(consumer);
169      }
170    },
171    NO_SPLIT_TRY_ADVANCE {
172      @Override
173      <E extends @Nullable Object> void forEach(
174          GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
175        while (spliterator.tryAdvance(consumer)) {
176          // do nothing
177        }
178      }
179    },
180    MAXIMUM_SPLIT {
181      @Override
182      <E extends @Nullable Object> void forEach(
183          GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
184        for (GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator);
185            prefix != null;
186            prefix = trySplitTestingSize(spliterator)) {
187          forEach(prefix, consumer);
188        }
189        long size = spliterator.getExactSizeIfKnown();
190        long[] counter = {0};
191        spliterator.forEachRemaining(
192            e -> {
193              consumer.accept(e);
194              counter[0]++;
195            });
196        if (size >= 0) {
197          assertEquals(size, counter[0]);
198        }
199      }
200    },
201    ALTERNATE_ADVANCE_AND_SPLIT {
202      @Override
203      <E extends @Nullable Object> void forEach(
204          GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
205        while (spliterator.tryAdvance(consumer)) {
206          GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator);
207          if (prefix != null) {
208            forEach(prefix, consumer);
209          }
210        }
211      }
212    };
213
214    abstract <E extends @Nullable Object> void forEach(
215        GeneralSpliterator<E> spliterator, Consumer<? super E> consumer);
216
217    static final Set<SpliteratorDecompositionStrategy> ALL_STRATEGIES =
218        unmodifiableSet(new LinkedHashSet<>(asList(values())));
219  }
220
221  private static <E extends @Nullable Object> @Nullable GeneralSpliterator<E> trySplitTestingSize(
222      GeneralSpliterator<E> spliterator) {
223    boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED);
224    long originalSize = spliterator.estimateSize();
225    GeneralSpliterator<E> trySplit = spliterator.trySplit();
226    if (spliterator.estimateSize() > originalSize) {
227      fail(
228          format(
229              "estimated size of spliterator after trySplit (%s) is larger than original size (%s)",
230              spliterator.estimateSize(), originalSize));
231    }
232    if (trySplit != null) {
233      if (trySplit.estimateSize() > originalSize) {
234        fail(
235            format(
236                "estimated size of trySplit result (%s) is larger than original size (%s)",
237                trySplit.estimateSize(), originalSize));
238      }
239    }
240    if (subsized) {
241      if (trySplit != null) {
242        assertEquals(
243            "sum of estimated sizes of trySplit and original spliterator after trySplit",
244            originalSize,
245            trySplit.estimateSize() + spliterator.estimateSize());
246      } else {
247        assertEquals(
248            "estimated size of spliterator after failed trySplit",
249            originalSize,
250            spliterator.estimateSize());
251      }
252    }
253    return trySplit;
254  }
255
256  public static <E extends @Nullable Object> SpliteratorTester<E> of(
257      Supplier<Spliterator<E>> spliteratorSupplier) {
258    return new SpliteratorTester<>(
259        ImmutableSet.of(() -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get())));
260  }
261
262  /**
263   * @since 33.4.0 (but since 28.1 in the JRE flavor)
264   */
265  public static SpliteratorTester<Integer> ofInt(Supplier<Spliterator.OfInt> spliteratorSupplier) {
266    return new SpliteratorTester<>(
267        ImmutableSet.of(
268            () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()),
269            () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept)));
270  }
271
272  /**
273   * @since 33.4.0 (but since 28.1 in the JRE flavor)
274   */
275  public static SpliteratorTester<Long> ofLong(Supplier<Spliterator.OfLong> spliteratorSupplier) {
276    return new SpliteratorTester<>(
277        ImmutableSet.of(
278            () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()),
279            () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept)));
280  }
281
282  /**
283   * @since 33.4.0 (but since 28.1 in the JRE flavor)
284   */
285  public static SpliteratorTester<Double> ofDouble(
286      Supplier<Spliterator.OfDouble> spliteratorSupplier) {
287    return new SpliteratorTester<>(
288        ImmutableSet.of(
289            () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()),
290            () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept)));
291  }
292
293  private final ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers;
294
295  private SpliteratorTester(ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers) {
296    this.spliteratorSuppliers = checkNotNull(spliteratorSuppliers);
297  }
298
299  @SafeVarargs
300  @CanIgnoreReturnValue
301  public final Ordered expect(Object... elements) {
302    return expect(asList(elements));
303  }
304
305  @CanIgnoreReturnValue
306  public final Ordered expect(Iterable<?> elements) {
307    List<List<E>> resultsForAllStrategies = new ArrayList<>();
308    for (Supplier<GeneralSpliterator<E>> spliteratorSupplier : spliteratorSuppliers) {
309      GeneralSpliterator<E> spliterator = spliteratorSupplier.get();
310      int characteristics = spliterator.characteristics();
311      long estimatedSize = spliterator.estimateSize();
312      for (SpliteratorDecompositionStrategy strategy :
313          SpliteratorDecompositionStrategy.ALL_STRATEGIES) {
314        List<E> resultsForStrategy = new ArrayList<>();
315        strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add);
316
317        // TODO(cpovirk): better failure messages
318        if ((characteristics & Spliterator.NONNULL) != 0) {
319          assertFalse(resultsForStrategy.contains(null));
320        }
321        if ((characteristics & Spliterator.SORTED) != 0) {
322          Comparator<? super E> comparator = spliterator.getComparator();
323          if (comparator == null) {
324            // A sorted spliterator with no comparator is already using natural order.
325            // (We could probably find a way to avoid rawtypes here if we wanted.)
326            @SuppressWarnings({"unchecked", "rawtypes"})
327            Comparator<? super E> naturalOrder =
328                (Comparator<? super E>) Comparator.<Comparable>naturalOrder();
329            comparator = naturalOrder;
330          }
331          assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy));
332        }
333        if ((characteristics & Spliterator.SIZED) != 0) {
334          assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size());
335        }
336
337        assertEqualIgnoringOrder(elements, resultsForStrategy);
338        resultsForAllStrategies.add(resultsForStrategy);
339      }
340    }
341    return new Ordered() {
342      @Override
343      public void inOrder() {
344        for (List<E> resultsForStrategy : resultsForAllStrategies) {
345          assertEqualInOrder(elements, resultsForStrategy);
346        }
347      }
348    };
349  }
350}