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