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