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}