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