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}