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.testing; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020import static junit.framework.Assert.assertTrue; 021 022import com.google.common.annotations.GwtCompatible; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.EnumSet; 027import java.util.List; 028import java.util.Objects; 029import java.util.function.BiPredicate; 030import java.util.stream.Collector; 031import org.checkerframework.checker.nullness.qual.Nullable; 032 033/** 034 * Tester for {@code Collector} implementations. 035 * 036 * <p>Example usage: 037 * 038 * <pre> 039 * CollectorTester.of(Collectors.summingInt(Integer::parseInt)) 040 * .expectCollects(3, "1", "2") 041 * .expectCollects(10, "1", "4", "3", "2") 042 * .expectCollects(5, "-3", "0", "8"); 043 * </pre> 044 * 045 * @author Louis Wasserman 046 * @since 21.0 047 */ 048@GwtCompatible 049public final class CollectorTester<T, A, R> { 050 /** 051 * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code 052 * Collector} will be compared to the expected value using {@link Object.equals}. 053 */ 054 public static <T, A, R> CollectorTester<T, A, R> of(Collector<T, A, R> collector) { 055 return of(collector, Objects::equals); 056 } 057 058 /** 059 * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code 060 * Collector} will be compared to the expected value using the specified {@code equivalence}. 061 */ 062 public static <T, A, R> CollectorTester<T, A, R> of( 063 Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) { 064 return new CollectorTester<>(collector, equivalence); 065 } 066 067 private final Collector<T, A, R> collector; 068 private final BiPredicate<? super R, ? super R> equivalence; 069 070 private CollectorTester( 071 Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) { 072 this.collector = checkNotNull(collector); 073 this.equivalence = checkNotNull(equivalence); 074 } 075 076 /** 077 * Different orderings for combining the elements of an input array, which must all produce the 078 * same result. 079 */ 080 enum CollectStrategy { 081 /** Get one accumulator and accumulate the elements into it sequentially. */ 082 SEQUENTIAL { 083 @Override 084 final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) { 085 A accum = collector.supplier().get(); 086 for (T input : inputs) { 087 collector.accumulator().accept(accum, input); 088 } 089 return accum; 090 } 091 }, 092 /** Get one accumulator for each element and merge the accumulators left-to-right. */ 093 MERGE_LEFT_ASSOCIATIVE { 094 @Override 095 final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) { 096 A accum = collector.supplier().get(); 097 for (T input : inputs) { 098 A newAccum = collector.supplier().get(); 099 collector.accumulator().accept(newAccum, input); 100 accum = collector.combiner().apply(accum, newAccum); 101 } 102 return accum; 103 } 104 }, 105 /** Get one accumulator for each element and merge the accumulators right-to-left. */ 106 MERGE_RIGHT_ASSOCIATIVE { 107 @Override 108 final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) { 109 List<A> stack = new ArrayList<>(); 110 for (T input : inputs) { 111 A newAccum = collector.supplier().get(); 112 collector.accumulator().accept(newAccum, input); 113 push(stack, newAccum); 114 } 115 push(stack, collector.supplier().get()); 116 while (stack.size() > 1) { 117 A right = pop(stack); 118 A left = pop(stack); 119 push(stack, collector.combiner().apply(left, right)); 120 } 121 return pop(stack); 122 } 123 124 <E> void push(List<E> stack, E value) { 125 stack.add(value); 126 } 127 128 <E> E pop(List<E> stack) { 129 return stack.remove(stack.size() - 1); 130 } 131 }; 132 133 abstract <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs); 134 } 135 136 /** 137 * Verifies that the specified expected result is always produced by collecting the specified 138 * inputs, regardless of how the elements are divided. 139 */ 140 @SafeVarargs 141 public final CollectorTester<T, A, R> expectCollects(@Nullable R expectedResult, T... inputs) { 142 List<T> list = Arrays.asList(inputs); 143 doExpectCollects(expectedResult, list); 144 if (collector.characteristics().contains(Collector.Characteristics.UNORDERED)) { 145 Collections.reverse(list); 146 doExpectCollects(expectedResult, list); 147 } 148 return this; 149 } 150 151 private void doExpectCollects(@Nullable R expectedResult, List<T> inputs) { 152 for (CollectStrategy scheme : EnumSet.allOf(CollectStrategy.class)) { 153 A finalAccum = scheme.result(collector, inputs); 154 if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { 155 assertEquivalent(expectedResult, (R) finalAccum); 156 } 157 assertEquivalent(expectedResult, collector.finisher().apply(finalAccum)); 158 } 159 } 160 161 private void assertEquivalent(@Nullable R expected, @Nullable R actual) { 162 assertTrue( 163 "Expected " + expected + " got " + actual + " modulo equivalence " + equivalence, 164 equivalence.test(expected, actual)); 165 } 166}