aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/java/com/google/common/truth/MultimapSubject.java
diff options
context:
space:
mode:
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.java164
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));
}
}