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