diff options
Diffstat (limited to 'core/src/main/java/com/google/common/truth/MultimapSubject.java')
-rw-r--r-- | core/src/main/java/com/google/common/truth/MultimapSubject.java | 164 |
1 files changed, 83 insertions, 81 deletions
diff --git a/core/src/main/java/com/google/common/truth/MultimapSubject.java b/core/src/main/java/com/google/common/truth/MultimapSubject.java index 332da4ae..4a02216f 100644 --- a/core/src/main/java/com/google/common/truth/MultimapSubject.java +++ b/core/src/main/java/com/google/common/truth/MultimapSubject.java @@ -40,6 +40,7 @@ import com.google.common.collect.Sets; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -56,14 +57,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class MultimapSubject extends Subject { /** Ordered implementation that does nothing because an earlier check already caused a failure. */ - @SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility - private static final Ordered ALREADY_FAILED = - new Ordered() { - @Override - public void inOrder() {} - }; + private static final Ordered ALREADY_FAILED = () -> {}; - private final Multimap<?, ?> actual; + private final @Nullable Multimap<?, ?> actual; /** * Constructor for use by subclasses. If you want to create an instance of this class itself, call @@ -83,14 +79,14 @@ public class MultimapSubject extends Subject { /** Fails if the multimap is not empty. */ public final void isEmpty() { - if (!actual.isEmpty()) { + if (!checkNotNull(actual).isEmpty()) { failWithActual(simpleFact("expected to be empty")); } } /** Fails if the multimap is empty. */ public final void isNotEmpty() { - if (actual.isEmpty()) { + if (checkNotNull(actual).isEmpty()) { failWithoutActual(simpleFact("expected not to be empty")); } } @@ -98,25 +94,27 @@ public class MultimapSubject extends Subject { /** Fails if the multimap does not have the given size. */ public final void hasSize(int expectedSize) { checkArgument(expectedSize >= 0, "expectedSize(%s) must be >= 0", expectedSize); - check("size()").that(actual.size()).isEqualTo(expectedSize); + check("size()").that(checkNotNull(actual).size()).isEqualTo(expectedSize); } /** Fails if the multimap does not contain the given key. */ public final void containsKey(@Nullable Object key) { - check("keySet()").that(actual.keySet()).contains(key); + check("keySet()").that(checkNotNull(actual).keySet()).contains(key); } /** Fails if the multimap contains the given key. */ public final void doesNotContainKey(@Nullable Object key) { - check("keySet()").that(actual.keySet()).doesNotContain(key); + check("keySet()").that(checkNotNull(actual).keySet()).doesNotContain(key); } /** Fails if the multimap does not contain the given entry. */ public final void containsEntry(@Nullable Object key, @Nullable Object value) { // TODO(kak): Can we share any of this logic w/ MapSubject.containsEntry()? + checkNotNull(actual); if (!actual.containsEntry(key, value)) { - Map.Entry<Object, Object> entry = immutableEntry(key, value); - List<Map.Entry<Object, Object>> entryList = ImmutableList.of(entry); + Map.Entry<@Nullable Object, @Nullable Object> entry = immutableEntry(key, value); + ImmutableList<Map.Entry<@Nullable Object, @Nullable Object>> entryList = + ImmutableList.of(entry); // TODO(cpovirk): If the key is present but not with the right value, we could fail using // something like valuesForKey(key).contains(value). Consider whether this is worthwhile. if (hasMatchingToStringPair(actual.entries(), entryList)) { @@ -136,7 +134,7 @@ public class MultimapSubject extends Subject { fact("though it did contain values with that key", actual.asMap().get(key)), fact("full contents", actualCustomStringRepresentationForPackageMembersToCall())); } else if (actual.containsValue(value)) { - Set<Object> keys = new LinkedHashSet<>(); + Set<@Nullable Object> keys = new LinkedHashSet<>(); for (Map.Entry<?, ?> actualEntry : actual.entries()) { if (Objects.equal(actualEntry.getValue(), value)) { keys.add(actualEntry.getKey()); @@ -156,7 +154,7 @@ public class MultimapSubject extends Subject { /** Fails if the multimap contains the given entry. */ public final void doesNotContainEntry(@Nullable Object key, @Nullable Object value) { checkNoNeedToDisplayBothValues("entries()") - .that(actual.entries()) + .that(checkNotNull(actual).entries()) .doesNotContain(immutableEntry(key, value)); } @@ -177,7 +175,8 @@ public class MultimapSubject extends Subject { * currently they must perform it _after_. */ public IterableSubject valuesForKey(@Nullable Object key) { - return check("valuesForKey(%s)", key).that(((Multimap<Object, Object>) actual).get(key)); + return check("valuesForKey(%s)", key) + .that(((Multimap<@Nullable Object, @Nullable Object>) checkNotNull(actual)).get(key)); } @Override @@ -202,9 +201,9 @@ public class MultimapSubject extends Subject { lenientFormat( "a %s cannot equal a %s if either is non-empty", actualType, otherType))); } else if (actual instanceof ListMultimap) { - containsExactlyEntriesIn((Multimap<?, ?>) other).inOrder(); + containsExactlyEntriesIn((Multimap<?, ?>) checkNotNull(other)).inOrder(); } else if (actual instanceof SetMultimap) { - containsExactlyEntriesIn((Multimap<?, ?>) other); + containsExactlyEntriesIn((Multimap<?, ?>) checkNotNull(other)); } else { super.isEqualTo(other); } @@ -221,6 +220,7 @@ public class MultimapSubject extends Subject { @CanIgnoreReturnValue public final Ordered containsExactlyEntriesIn(Multimap<?, ?> expectedMultimap) { checkNotNull(expectedMultimap, "expectedMultimap"); + checkNotNull(actual); ListMultimap<?, ?> missing = difference(expectedMultimap, actual); ListMultimap<?, ?> extra = difference(actual, expectedMultimap); @@ -276,6 +276,7 @@ public class MultimapSubject extends Subject { @CanIgnoreReturnValue public final Ordered containsAtLeastEntriesIn(Multimap<?, ?> expectedMultimap) { checkNotNull(expectedMultimap, "expectedMultimap"); + checkNotNull(actual); ListMultimap<?, ?> missing = difference(expectedMultimap, actual); // TODO(kak): Possible enhancement: Include "[1 copy]" if the element does appear in @@ -293,8 +294,9 @@ public class MultimapSubject extends Subject { /** Fails if the multimap is not empty. */ @CanIgnoreReturnValue + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() public final Ordered containsExactly() { - return check().about(iterableEntries()).that(actual.entries()).containsExactly(); + return check().about(iterableEntries()).that(checkNotNull(actual).entries()).containsExactly(); } /** @@ -305,7 +307,7 @@ public class MultimapSubject extends Subject { */ @CanIgnoreReturnValue public final Ordered containsExactly( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { return containsExactlyEntriesIn(accumulateMultimap(k0, v0, rest)); } @@ -317,19 +319,20 @@ public class MultimapSubject extends Subject { */ @CanIgnoreReturnValue public final Ordered containsAtLeast( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { return containsAtLeastEntriesIn(accumulateMultimap(k0, v0, rest)); } - private static Multimap<Object, Object> accumulateMultimap( - @Nullable Object k0, @Nullable Object v0, /*@Nullable*/ Object... rest) { + private static ListMultimap<@Nullable Object, @Nullable Object> accumulateMultimap( + @Nullable Object k0, @Nullable Object v0, @Nullable Object... rest) { checkArgument( rest.length % 2 == 0, "There must be an equal number of key/value pairs " + "(i.e., the number of key/value parameters (%s) must be even).", rest.length + 2); - LinkedListMultimap<Object, Object> expectedMultimap = LinkedListMultimap.create(); + LinkedListMultimap<@Nullable Object, @Nullable Object> expectedMultimap = + LinkedListMultimap.create(); expectedMultimap.put(k0, v0); for (int i = 0; i < rest.length; i += 2) { expectedMultimap.put(rest[i], rest[i + 1]); @@ -340,8 +343,8 @@ public class MultimapSubject extends Subject { private Factory<IterableSubject, Iterable<?>> iterableEntries() { return new Factory<IterableSubject, Iterable<?>>() { @Override - public IterableSubject createSubject(FailureMetadata metadata, Iterable<?> actual) { - return new IterableEntries(metadata, MultimapSubject.this, actual); + public IterableSubject createSubject(FailureMetadata metadata, @Nullable Iterable<?> actual) { + return new IterableEntries(metadata, MultimapSubject.this, checkNotNull(actual)); } }; } @@ -352,7 +355,7 @@ public class MultimapSubject extends Subject { IterableEntries(FailureMetadata metadata, MultimapSubject multimapSubject, Iterable<?> actual) { super(metadata, actual); // We want to use the multimap's toString() instead of the iterable of entries' toString(): - this.stringRepresentation = multimapSubject.actual.toString(); + this.stringRepresentation = String.valueOf(multimapSubject.actual); } @Override @@ -379,18 +382,19 @@ public class MultimapSubject extends Subject { @Override public void inOrder() { // We use the fact that Sets.intersection's result has the same order as the first parameter + checkNotNull(actual); boolean keysInOrder = Lists.newArrayList(Sets.intersection(actual.keySet(), expectedMultimap.keySet())) .equals(Lists.newArrayList(expectedMultimap.keySet())); - LinkedHashSet<Object> keysWithValuesOutOfOrder = Sets.newLinkedHashSet(); + LinkedHashSet<@Nullable Object> keysWithValuesOutOfOrder = Sets.newLinkedHashSet(); for (Object key : expectedMultimap.keySet()) { List<?> actualVals = Lists.newArrayList(get(actual, key)); List<?> expectedVals = Lists.newArrayList(get(expectedMultimap, key)); Iterator<?> actualIterator = actualVals.iterator(); for (Object value : expectedVals) { if (!advanceToFind(actualIterator, value)) { - keysWithValuesOutOfOrder.add(key); + boolean unused = keysWithValuesOutOfOrder.add(key); break; } } @@ -432,7 +436,7 @@ public class MultimapSubject extends Subject { * where the contract explicitly states that the iterator isn't advanced beyond the value if the * value is found. */ - private static boolean advanceToFind(Iterator<?> iterator, Object value) { + private static boolean advanceToFind(Iterator<?> iterator, @Nullable Object value) { while (iterator.hasNext()) { if (Objects.equal(iterator.next(), value)) { return true; @@ -441,16 +445,18 @@ public class MultimapSubject extends Subject { return false; } - private static <V> Collection<V> get(Multimap<?, V> multimap, @Nullable Object key) { + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable types + private static <V extends @Nullable Object> Collection<V> get( + Multimap<?, V> multimap, @Nullable Object key) { if (multimap.containsKey(key)) { - return multimap.asMap().get(key); + return checkNotNull(multimap.asMap().get(key)); } else { - return ImmutableList.of(); + return Collections.emptyList(); } } private static ListMultimap<?, ?> difference(Multimap<?, ?> minuend, Multimap<?, ?> subtrahend) { - ListMultimap<Object, Object> difference = LinkedListMultimap.create(); + ListMultimap<@Nullable Object, @Nullable Object> difference = LinkedListMultimap.create(); for (Object key : minuend.keySet()) { List<?> valDifference = difference( @@ -461,8 +467,9 @@ public class MultimapSubject extends Subject { } private static List<?> difference(List<?> minuend, List<?> subtrahend) { - LinkedHashMultiset<Object> remaining = LinkedHashMultiset.<Object>create(subtrahend); - List<Object> difference = Lists.newArrayList(); + LinkedHashMultiset<@Nullable Object> remaining = + LinkedHashMultiset.<@Nullable Object>create(subtrahend); + List<@Nullable Object> difference = Lists.newArrayList(); for (Object elem : minuend) { if (!remaining.remove(elem)) { difference.add(elem); @@ -492,11 +499,15 @@ public class MultimapSubject extends Subject { */ private static Multimap<?, ?> annotateEmptyStringsMultimap(Multimap<?, ?> multimap) { if (multimap.containsKey("") || multimap.containsValue("")) { - ListMultimap<Object, Object> annotatedMultimap = LinkedListMultimap.create(); + ListMultimap<@Nullable Object, @Nullable Object> annotatedMultimap = + LinkedListMultimap.create(); for (Map.Entry<?, ?> entry : multimap.entries()) { - Object key = "".equals(entry.getKey()) ? HUMAN_UNDERSTANDABLE_EMPTY_STRING : entry.getKey(); + Object key = + Objects.equal(entry.getKey(), "") ? HUMAN_UNDERSTANDABLE_EMPTY_STRING : entry.getKey(); Object value = - "".equals(entry.getValue()) ? HUMAN_UNDERSTANDABLE_EMPTY_STRING : entry.getValue(); + Objects.equal(entry.getValue(), "") + ? HUMAN_UNDERSTANDABLE_EMPTY_STRING + : entry.getValue(); annotatedMultimap.put(key, value); } return annotatedMultimap; @@ -526,8 +537,9 @@ public class MultimapSubject extends Subject { * <p>Any of the methods on the returned object may throw {@link ClassCastException} if they * encounter an actual value that is not of type {@code A}. */ - public <A, E> UsingCorrespondence<A, E> comparingValuesUsing( - Correspondence<? super A, ? super E> correspondence) { + public <A extends @Nullable Object, E extends @Nullable Object> + UsingCorrespondence<A, E> comparingValuesUsing( + Correspondence<? super A, ? super E> correspondence) { return new UsingCorrespondence<>(correspondence); } @@ -542,7 +554,7 @@ public class MultimapSubject extends Subject { * * <p>Note that keys will always be compared with regular object equality ({@link Object#equals}). */ - public final class UsingCorrespondence<A, E> { + public final class UsingCorrespondence<A extends @Nullable Object, E extends @Nullable Object> { private final Correspondence<? super A, ? super E> correspondence; @@ -554,10 +566,10 @@ public class MultimapSubject extends Subject { * Fails if the multimap does not contain an entry with the given key and a value that * corresponds to the given value. */ - public void containsEntry(@Nullable Object expectedKey, @Nullable E expectedValue) { - if (actual.containsKey(expectedKey)) { + public void containsEntry(@Nullable Object expectedKey, E expectedValue) { + if (checkNotNull(actual).containsKey(expectedKey)) { // Found matching key. - Collection<A> actualValues = getCastActual().asMap().get(expectedKey); + Collection<A> actualValues = checkNotNull(getCastActual().asMap().get(expectedKey)); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (A actualValue : actualValues) { if (correspondence.safeCompare(actualValue, expectedValue, exceptions)) { @@ -647,9 +659,9 @@ public class MultimapSubject extends Subject { * Fails if the multimap contains an entry with the given key and a value that corresponds to * the given value. */ - public void doesNotContainEntry(@Nullable Object excludedKey, @Nullable E excludedValue) { - if (actual.containsKey(excludedKey)) { - Collection<A> actualValues = getCastActual().asMap().get(excludedKey); + public void doesNotContainEntry(@Nullable Object excludedKey, E excludedValue) { + if (checkNotNull(actual).containsKey(excludedKey)) { + Collection<A> actualValues = checkNotNull(getCastActual().asMap().get(excludedKey)); List<A> matchingValues = new ArrayList<>(); Correspondence.ExceptionStore exceptions = Correspondence.ExceptionStore.forMapValues(); for (A actualValue : actualValues) { @@ -714,7 +726,8 @@ public class MultimapSubject extends Subject { * public containsExactlyEntriesIn method. This is recommended by Effective Java item 31 (3rd * edition). */ - private <K, V extends E> Ordered internalContainsExactlyEntriesIn( + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() + private <K extends @Nullable Object, V extends E> Ordered internalContainsExactlyEntriesIn( Multimap<K, V> expectedMultimap) { // Note: The non-fuzzy MultimapSubject.containsExactlyEntriesIn has a custom implementation // and produces somewhat better failure messages simply asserting about the iterables of @@ -724,8 +737,8 @@ public class MultimapSubject extends Subject { // complexity for little gain. return check() .about(iterableEntries()) - .that(actual.entries()) - .comparingElementsUsing(new EntryCorrespondence<K, A, V>(correspondence)) + .that(checkNotNull(actual).entries()) + .comparingElementsUsing(MultimapSubject.<K, A, V>entryCorrespondence(correspondence)) .containsExactlyElementsIn(expectedMultimap.entries()); } @@ -748,7 +761,8 @@ public class MultimapSubject extends Subject { * public containsAtLeastEntriesIn method. This is recommended by Effective Java item 31 (3rd * edition). */ - private <K, V extends E> Ordered internalContainsAtLeastEntriesIn( + @SuppressWarnings("deprecation") // TODO(b/134064106): design an alternative to no-arg check() + private <K extends @Nullable Object, V extends E> Ordered internalContainsAtLeastEntriesIn( Multimap<K, V> expectedMultimap) { // Note: The non-fuzzy MultimapSubject.containsAtLeastEntriesIn has a custom implementation // and produces somewhat better failure messages simply asserting about the iterables of @@ -758,8 +772,8 @@ public class MultimapSubject extends Subject { // complexity for little gain. return check() .about(iterableEntries()) - .that(actual.entries()) - .comparingElementsUsing(new EntryCorrespondence<K, A, V>(correspondence)) + .that(checkNotNull(actual).entries()) + .comparingElementsUsing(MultimapSubject.<K, A, V>entryCorrespondence(correspondence)) .containsAtLeastElementsIn(expectedMultimap.entries()); } @@ -770,8 +784,7 @@ public class MultimapSubject extends Subject { * key/value pairs at compile time. Please make sure you provide varargs in key/value pairs! */ @CanIgnoreReturnValue - public Ordered containsExactly( - @Nullable Object k0, @Nullable E v0, /*@Nullable*/ Object... rest) { + public Ordered containsExactly(@Nullable Object k0, @Nullable E v0, @Nullable Object... rest) { @SuppressWarnings("unchecked") Multimap<?, E> expectedMultimap = (Multimap<?, E>) accumulateMultimap(k0, v0, rest); return containsExactlyEntriesIn(expectedMultimap); @@ -790,8 +803,7 @@ public class MultimapSubject extends Subject { * key/value pairs at compile time. Please make sure you provide varargs in key/value pairs! */ @CanIgnoreReturnValue - public Ordered containsAtLeast( - @Nullable Object k0, @Nullable E v0, /*@Nullable*/ Object... rest) { + public Ordered containsAtLeast(@Nullable Object k0, @Nullable E v0, @Nullable Object... rest) { @SuppressWarnings("unchecked") Multimap<?, E> expectedMultimap = (Multimap<?, E>) accumulateMultimap(k0, v0, rest); return containsAtLeastEntriesIn(expectedMultimap); @@ -799,30 +811,20 @@ public class MultimapSubject extends Subject { @SuppressWarnings("unchecked") // throwing ClassCastException is the correct behaviour private Multimap<?, A> getCastActual() { - return (Multimap<?, A>) actual; + return (Multimap<?, A>) checkNotNull(actual); } } - private static final class EntryCorrespondence<K, A, E> - extends Correspondence<Map.Entry<K, A>, Map.Entry<K, E>> { - - private final Correspondence<? super A, ? super E> valueCorrespondence; - - EntryCorrespondence(Correspondence<? super A, ? super E> valueCorrespondence) { - this.valueCorrespondence = valueCorrespondence; - } - - @Override - public boolean compare(Map.Entry<K, A> actual, Map.Entry<K, E> expected) { - return Objects.equal(actual.getKey(), expected.getKey()) - && valueCorrespondence.compare(actual.getValue(), expected.getValue()); - } - - @Override - public String toString() { - return lenientFormat( - "has a key that is equal to and a value that %s the key and value of", - valueCorrespondence); - } + private static < + K extends @Nullable Object, A extends @Nullable Object, E extends @Nullable Object> + Correspondence<Map.Entry<K, A>, Map.Entry<K, E>> entryCorrespondence( + Correspondence<? super A, ? super E> valueCorrespondence) { + return Correspondence.from( + (Map.Entry<K, A> actual, Map.Entry<K, E> expected) -> + Objects.equal(actual.getKey(), expected.getKey()) + && valueCorrespondence.compare(actual.getValue(), expected.getValue()), + lenientFormat( + "has a key that is equal to and a value that %s the key and value of", + valueCorrespondence)); } } |