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}