blob: 209c867e8b28087631573175d27c86af0dd39fcb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.beam.sdk.testing;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.core.Is.is;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.coders.CoderException;
import org.apache.beam.sdk.coders.ListCoder;
import org.apache.beam.sdk.util.CoderUtils;
import org.apache.beam.sdk.util.UserCodeException;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.MoreObjects;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
/**
* Static class for building and using {@link SerializableMatcher} instances.
*
* <p>Most matchers are wrappers for hamcrest's {@link Matchers}. Please be familiar with the
* documentation there. Values retained by a {@link SerializableMatcher} are required to be
* serializable, either via Java serialization or via a provided {@link Coder}.
*
* <p>The following matchers are novel to Apache Beam:
*
* <ul>
* <li>{@link #kvWithKey} for matching just the key of a {@link KV}.
* <li>{@link #kvWithValue} for matching just the value of a {@link KV}.
* <li>{@link #kv} for matching the key and value of a {@link KV}.
* </ul>
*
* <p>For example, to match a group from {@link org.apache.beam.sdk.transforms.GroupByKey}, which
* has type {@code KV<K, Iterable<V>>} for some {@code K} and {@code V} and where the order of the
* iterable is undefined, use a matcher like {@code kv(equalTo("some key"), containsInAnyOrder(1, 2,
* 3))}.
*/
public class SerializableMatchers implements Serializable {
// Serializable only because of capture by anonymous inner classes
private SerializableMatchers() {} // not instantiable
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#allOf(Iterable)}. */
public static <T> SerializableMatcher<T> allOf(
Iterable<SerializableMatcher<? super T>> serializableMatchers) {
@SuppressWarnings({"rawtypes", "unchecked"}) // safe covariant cast
final Iterable<Matcher<? super T>> matchers = (Iterable) serializableMatchers;
return fromSupplier(() -> Matchers.allOf(matchers));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#allOf(Matcher[])}. */
@SafeVarargs
public static <T> SerializableMatcher<T> allOf(final SerializableMatcher<T>... matchers) {
return fromSupplier(() -> Matchers.allOf(matchers));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#anyOf(Iterable)}. */
public static <T> SerializableMatcher<T> anyOf(
Iterable<SerializableMatcher<? super T>> serializableMatchers) {
@SuppressWarnings({"rawtypes", "unchecked"}) // safe covariant cast
final Iterable<Matcher<? super T>> matchers = (Iterable) serializableMatchers;
return fromSupplier(() -> Matchers.anyOf(matchers));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#anyOf(Matcher[])}. */
@SafeVarargs
public static <T> SerializableMatcher<T> anyOf(final SerializableMatcher<T>... matchers) {
return fromSupplier(() -> Matchers.anyOf(matchers));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#anything()}. */
public static SerializableMatcher<Object> anything() {
return fromSupplier(Matchers::anything);
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContaining(Object[])}.
*/
@SafeVarargs
public static <T extends Serializable> SerializableMatcher<T[]> arrayContaining(
final T... items) {
return fromSupplier(() -> Matchers.arrayContaining(items));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContaining(Object[])}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. They are
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
@SafeVarargs
public static <T> SerializableMatcher<T[]> arrayContaining(Coder<T> coder, T... items) {
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> Matchers.arrayContaining(itemsSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContaining(Matcher[])}.
*/
@SafeVarargs
public static <T> SerializableMatcher<T[]> arrayContaining(
final SerializableMatcher<? super T>... matchers) {
return fromSupplier(() -> Matchers.arrayContaining(matchers));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContaining(List)}.
*/
public static <T> SerializableMatcher<T[]> arrayContaining(
List<SerializableMatcher<? super T>> serializableMatchers) {
@SuppressWarnings({"rawtypes", "unchecked"}) // safe covariant cast
final List<Matcher<? super T>> matchers = (List) serializableMatchers;
return fromSupplier(() -> Matchers.arrayContaining(matchers));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContainingInAnyOrder(Object[])}.
*/
@SafeVarargs
public static <T extends Serializable> SerializableMatcher<T[]> arrayContainingInAnyOrder(
final T... items) {
return fromSupplier(() -> Matchers.arrayContainingInAnyOrder(items));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContainingInAnyOrder(Object[])}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. They are
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
@SafeVarargs
public static <T> SerializableMatcher<T[]> arrayContainingInAnyOrder(Coder<T> coder, T... items) {
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> Matchers.arrayContaining(itemsSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContainingInAnyOrder(Matcher[])}.
*/
@SafeVarargs
public static <T> SerializableMatcher<T[]> arrayContainingInAnyOrder(
final SerializableMatcher<? super T>... matchers) {
return fromSupplier(() -> Matchers.arrayContainingInAnyOrder(matchers));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayContainingInAnyOrder(Collection)}.
*/
public static <T> SerializableMatcher<T[]> arrayContainingInAnyOrder(
Collection<SerializableMatcher<? super T>> serializableMatchers) {
@SuppressWarnings({"rawtypes", "unchecked"}) // safe covariant cast
final Collection<Matcher<? super T>> matchers = (Collection) serializableMatchers;
return fromSupplier(() -> Matchers.arrayContainingInAnyOrder(matchers));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#arrayWithSize(int)}.
*/
public static <T> SerializableMatcher<T[]> arrayWithSize(final int size) {
return fromSupplier(() -> Matchers.arrayWithSize(size));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#arrayWithSize(Matcher)}.
*/
public static <T> SerializableMatcher<T[]> arrayWithSize(
final SerializableMatcher<? super Integer> sizeMatcher) {
return fromSupplier(() -> Matchers.arrayWithSize(sizeMatcher));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#closeTo(double,double)}.
*/
public static SerializableMatcher<Double> closeTo(final double target, final double error) {
return fromSupplier(() -> Matchers.closeTo(target, error));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#contains(Object[])}.
*/
@SafeVarargs
public static <T extends Serializable> SerializableMatcher<Iterable<? extends T>> contains(
final T... items) {
return fromSupplier(() -> Matchers.contains(items));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#contains(Object[])}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. They are
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
@SafeVarargs
public static <T> SerializableMatcher<Iterable<? extends T>> contains(
Coder<T> coder, T... items) {
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> Matchers.containsInAnyOrder(itemsSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#contains(Matcher[])}.
*/
@SafeVarargs
public static <T> SerializableMatcher<Iterable<? extends T>> contains(
final SerializableMatcher<? super T>... matchers) {
return fromSupplier(() -> Matchers.contains(matchers));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#contains(List)}. */
public static <T extends Serializable> SerializableMatcher<Iterable<? extends T>> contains(
List<SerializableMatcher<? super T>> serializableMatchers) {
@SuppressWarnings({"rawtypes", "unchecked"}) // safe covariant cast
final List<Matcher<? super T>> matchers = (List) serializableMatchers;
return fromSupplier(() -> Matchers.contains(matchers));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#containsInAnyOrder(Object[])}.
*/
@SafeVarargs
public static <T extends Serializable>
SerializableMatcher<Iterable<? extends T>> containsInAnyOrder(final T... items) {
return fromSupplier(() -> Matchers.containsInAnyOrder(items));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#containsInAnyOrder(Object[])}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. It is
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
@SafeVarargs
public static <T> SerializableMatcher<Iterable<? extends T>> containsInAnyOrder(
Coder<T> coder, T... items) {
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> Matchers.containsInAnyOrder(itemsSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#containsInAnyOrder(Matcher[])}.
*/
@SafeVarargs
public static <T> SerializableMatcher<Iterable<? extends T>> containsInAnyOrder(
final SerializableMatcher<? super T>... matchers) {
return fromSupplier(() -> Matchers.containsInAnyOrder(matchers));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#containsInAnyOrder(Collection)}.
*/
public static <T> SerializableMatcher<Iterable<? extends T>> containsInAnyOrder(
Collection<SerializableMatcher<? super T>> serializableMatchers) {
@SuppressWarnings({"rawtypes", "unchecked"}) // safe covariant cast
final Collection<Matcher<? super T>> matchers = (Collection) serializableMatchers;
return fromSupplier(() -> Matchers.containsInAnyOrder(matchers));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#containsString}. */
public static SerializableMatcher<String> containsString(final String substring) {
return fromSupplier(() -> Matchers.containsString(substring));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#empty()}. */
public static <T> SerializableMatcher<Collection<? extends T>> empty() {
return fromSupplier(Matchers::empty);
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#emptyArray()}. */
public static <T> SerializableMatcher<T[]> emptyArray() {
return fromSupplier(Matchers::emptyArray);
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#emptyIterable()}. */
public static <T> SerializableMatcher<Iterable<? extends T>> emptyIterable() {
return fromSupplier(Matchers::emptyIterable);
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#endsWith}. */
public static SerializableMatcher<String> endsWith(final String substring) {
return fromSupplier(() -> Matchers.endsWith(substring));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#equalTo(Object)}. */
public static <T extends Serializable> SerializableMatcher<T> equalTo(final T expected) {
return fromSupplier(() -> Matchers.equalTo(expected));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#equalTo(Object)}.
*
* <p>The expected value of type {@code T} will be serialized using the provided {@link Coder}. It
* is explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T> SerializableMatcher<T> equalTo(Coder<T> coder, T expected) {
final SerializableSupplier<T> expectedSupplier = new SerializableViaCoder<>(coder, expected);
return fromSupplier(() -> Matchers.equalTo(expectedSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#greaterThan(Comparable)}.
*/
public static <T extends Comparable<T> & Serializable> SerializableMatcher<T> greaterThan(
final T target) {
return fromSupplier(() -> Matchers.greaterThan(target));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#greaterThan(Comparable)}.
*
* <p>The target value of type {@code T} will be serialized using the provided {@link Coder}. It
* is explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T extends Comparable<T> & Serializable> SerializableMatcher<T> greaterThan(
final Coder<T> coder, T target) {
final SerializableSupplier<T> targetSupplier = new SerializableViaCoder<>(coder, target);
return fromSupplier(() -> Matchers.greaterThan(targetSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#greaterThanOrEqualTo(Comparable)}.
*/
public static <T extends Comparable<T>> SerializableMatcher<T> greaterThanOrEqualTo(
final T target) {
return fromSupplier(() -> Matchers.greaterThanOrEqualTo(target));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#greaterThanOrEqualTo(Comparable)}.
*
* <p>The target value of type {@code T} will be serialized using the provided {@link Coder}. It
* is explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T extends Comparable<T> & Serializable>
SerializableMatcher<T> greaterThanOrEqualTo(final Coder<T> coder, T target) {
final SerializableSupplier<T> targetSupplier = new SerializableViaCoder<>(coder, target);
return fromSupplier(() -> Matchers.greaterThanOrEqualTo(targetSupplier.get()));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#hasItem(Object)}. */
public static <T extends Serializable> SerializableMatcher<Iterable<? super T>> hasItem(
final T target) {
return fromSupplier(() -> Matchers.hasItem(target));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#hasItem(Object)}.
*
* <p>The item of type {@code T} will be serialized using the provided {@link Coder}. It is
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T> SerializableMatcher<Iterable<? super T>> hasItem(Coder<T> coder, T target) {
final SerializableSupplier<T> targetSupplier = new SerializableViaCoder<>(coder, target);
return fromSupplier(() -> Matchers.hasItem(targetSupplier.get()));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#hasItem(Matcher)}. */
public static <T> SerializableMatcher<Iterable<? super T>> hasItem(
final SerializableMatcher<? super T> matcher) {
return fromSupplier(() -> Matchers.hasItem(matcher));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#hasSize(int)}. */
public static <T> SerializableMatcher<Collection<? extends T>> hasSize(final int size) {
return fromSupplier(() -> Matchers.hasSize(size));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#hasSize(Matcher)}. */
public static <T> SerializableMatcher<Collection<? extends T>> hasSize(
final SerializableMatcher<? super Integer> sizeMatcher) {
return fromSupplier(() -> Matchers.hasSize(sizeMatcher));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#iterableWithSize(int)}.
*/
public static <T> SerializableMatcher<Iterable<T>> iterableWithSize(final int size) {
return fromSupplier(() -> Matchers.iterableWithSize(size));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#iterableWithSize(Matcher)}.
*/
public static <T> SerializableMatcher<Iterable<T>> iterableWithSize(
final SerializableMatcher<? super Integer> sizeMatcher) {
return fromSupplier(() -> Matchers.iterableWithSize(sizeMatcher));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#isIn(Collection)}. */
public static <T extends Serializable> SerializableMatcher<T> isIn(
final Collection<T> collection) {
return fromSupplier(() -> is(in(collection)));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#isIn(Collection)}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. They are
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T> SerializableMatcher<T> isIn(Coder<T> coder, Collection<T> collection) {
@SuppressWarnings("unchecked")
T[] items = (T[]) collection.toArray();
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> is(in(itemsSupplier.get())));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#isIn(Object[])}. */
public static <T extends Serializable> SerializableMatcher<T> isIn(final T[] items) {
return fromSupplier(() -> is(in(items)));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#isIn(Object[])}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. They are
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T> SerializableMatcher<T> isIn(Coder<T> coder, T[] items) {
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> is(in(itemsSupplier.get())));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#isOneOf}. */
@SafeVarargs
public static <T extends Serializable> SerializableMatcher<T> isOneOf(final T... elems) {
return fromSupplier(() -> Matchers.isOneOf(elems));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#isOneOf}.
*
* <p>The items of type {@code T} will be serialized using the provided {@link Coder}. They are
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
@SafeVarargs
public static <T> SerializableMatcher<T> isOneOf(Coder<T> coder, T... items) {
final SerializableSupplier<T[]> itemsSupplier = new SerializableArrayViaCoder<>(coder, items);
return fromSupplier(() -> Matchers.isOneOf(itemsSupplier.get()));
}
/** A {@link SerializableMatcher} that matches any {@link KV} with the specified key. */
public static <K extends Serializable, V>
SerializableMatcher<KV<? extends K, ? extends V>> kvWithKey(K key) {
return new KvKeyMatcher<>(equalTo(key));
}
/**
* A {@link SerializableMatcher} that matches any {@link KV} with the specified key.
*
* <p>The key of type {@code K} will be serialized using the provided {@link Coder}. It is
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <K, V> SerializableMatcher<KV<? extends K, ? extends V>> kvWithKey(
Coder<K> coder, K key) {
return new KvKeyMatcher<>(equalTo(coder, key));
}
/** A {@link SerializableMatcher} that matches any {@link KV} with matching key. */
public static <K, V> SerializableMatcher<KV<? extends K, ? extends V>> kvWithKey(
final SerializableMatcher<? super K> keyMatcher) {
return new KvKeyMatcher<>(keyMatcher);
}
/** A {@link SerializableMatcher} that matches any {@link KV} with the specified value. */
public static <K, V extends Serializable>
SerializableMatcher<KV<? extends K, ? extends V>> kvWithValue(V value) {
return new KvValueMatcher<>(equalTo(value));
}
/**
* A {@link SerializableMatcher} that matches any {@link KV} with the specified value.
*
* <p>The value of type {@code V} will be serialized using the provided {@link Coder}. It is
* explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <K, V> SerializableMatcher<KV<? extends K, ? extends V>> kvWithValue(
Coder<V> coder, V value) {
return new KvValueMatcher<>(equalTo(coder, value));
}
/** A {@link SerializableMatcher} that matches any {@link KV} with matching value. */
public static <K, V> SerializableMatcher<KV<? extends K, ? extends V>> kvWithValue(
final SerializableMatcher<? super V> valueMatcher) {
return new KvValueMatcher<>(valueMatcher);
}
/** A {@link SerializableMatcher} that matches any {@link KV} with matching key and value. */
public static <K, V> SerializableMatcher<KV<? extends K, ? extends V>> kv(
final SerializableMatcher<? super K> keyMatcher,
final SerializableMatcher<? super V> valueMatcher) {
return SerializableMatchers.allOf(
SerializableMatchers.kvWithKey(keyMatcher), SerializableMatchers.kvWithValue(valueMatcher));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#lessThan(Comparable)}.
*/
public static <T extends Comparable<T> & Serializable> SerializableMatcher<T> lessThan(
final T target) {
return fromSupplier(() -> Matchers.lessThan(target));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link Matchers#lessThan(Comparable)}.
*
* <p>The target value of type {@code T} will be serialized using the provided {@link Coder}. It
* is explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T extends Comparable<T>> SerializableMatcher<T> lessThan(
Coder<T> coder, T target) {
final SerializableSupplier<T> targetSupplier = new SerializableViaCoder<>(coder, target);
return fromSupplier(() -> Matchers.lessThan(targetSupplier.get()));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#lessThanOrEqualTo(Comparable)}.
*/
public static <T extends Comparable<T> & Serializable> SerializableMatcher<T> lessThanOrEqualTo(
final T target) {
return fromSupplier(() -> Matchers.lessThanOrEqualTo(target));
}
/**
* A {@link SerializableMatcher} with identical criteria to {@link
* Matchers#lessThanOrEqualTo(Comparable)}.
*
* <p>The target value of type {@code T} will be serialized using the provided {@link Coder}. It
* is explicitly <i>not</i> required or expected to be serializable via Java serialization.
*/
public static <T extends Comparable<T>> SerializableMatcher<T> lessThanOrEqualTo(
Coder<T> coder, T target) {
final SerializableSupplier<T> targetSupplier = new SerializableViaCoder<>(coder, target);
return fromSupplier(() -> Matchers.lessThanOrEqualTo(targetSupplier.get()));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#not}. */
public static <T> SerializableMatcher<T> not(final SerializableMatcher<T> matcher) {
return fromSupplier(() -> Matchers.not(matcher));
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#nullValue}. */
public static SerializableMatcher<Object> nullValue() {
return fromSupplier(Matchers::nullValue);
}
/** A {@link SerializableMatcher} with identical criteria to {@link Matchers#startsWith}. */
public static SerializableMatcher<String> startsWith(final String substring) {
return fromSupplier(() -> Matchers.startsWith(substring));
}
private static class KvKeyMatcher<K, V> extends BaseMatcher<KV<? extends K, ? extends V>>
implements SerializableMatcher<KV<? extends K, ? extends V>> {
private final SerializableMatcher<? super K> keyMatcher;
public KvKeyMatcher(SerializableMatcher<? super K> keyMatcher) {
this.keyMatcher = keyMatcher;
}
@Override
public boolean matches(Object item) {
@SuppressWarnings("unchecked")
KV<K, ?> kvItem = (KV<K, ?>) item;
return keyMatcher.matches(kvItem.getKey());
}
@Override
public void describeMismatch(Object item, Description mismatchDescription) {
@SuppressWarnings("unchecked")
KV<K, ?> kvItem = (KV<K, ?>) item;
if (!keyMatcher.matches(kvItem.getKey())) {
mismatchDescription.appendText("key did not match: ");
keyMatcher.describeMismatch(kvItem.getKey(), mismatchDescription);
}
}
@Override
public void describeTo(Description description) {
description.appendText("KV with key matching ");
keyMatcher.describeTo(description);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(keyMatcher).toString();
}
}
private static class KvValueMatcher<K, V> extends BaseMatcher<KV<? extends K, ? extends V>>
implements SerializableMatcher<KV<? extends K, ? extends V>> {
private final SerializableMatcher<? super V> valueMatcher;
public KvValueMatcher(SerializableMatcher<? super V> valueMatcher) {
this.valueMatcher = valueMatcher;
}
@Override
public boolean matches(Object item) {
@SuppressWarnings("unchecked")
KV<?, V> kvItem = (KV<?, V>) item;
return valueMatcher.matches(kvItem.getValue());
}
@Override
public void describeMismatch(Object item, Description mismatchDescription) {
@SuppressWarnings("unchecked")
KV<?, V> kvItem = (KV<?, V>) item;
if (!valueMatcher.matches(kvItem.getValue())) {
mismatchDescription.appendText("value did not match: ");
valueMatcher.describeMismatch(kvItem.getValue(), mismatchDescription);
}
}
@Override
public void describeTo(Description description) {
description.appendText("KV with value matching ");
valueMatcher.describeTo(description);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(valueMatcher).toString();
}
}
/**
* Constructs a {@link SerializableMatcher} from a non-serializable {@link Matcher} via
* indirection through {@link SerializableSupplier}.
*
* <p>To wrap a {@link Matcher} which is not serializable, provide a {@link SerializableSupplier}
* with a {@link SerializableSupplier#get()} method that returns a fresh instance of the {@link
* Matcher} desired. The resulting {@link SerializableMatcher} will behave according to the {@link
* Matcher} returned by {@link SerializableSupplier#get() get()} when it is invoked during
* matching (which may occur on another machine).
*
* <pre>{@code
* return fromSupplier(new SerializableSupplier<Matcher<T>>() {
* * @Override
* public Matcher<T> get() {
* return new MyMatcherForT();
* }
* });
* }</pre>
*/
public static <T> SerializableMatcher<T> fromSupplier(SerializableSupplier<Matcher<T>> supplier) {
return new SerializableMatcherFromSupplier<>(supplier);
}
/**
* Supplies values of type {@code T}, and is serializable. Thus, even if {@code T} is not
* serializable, the supplier can be serialized and provide a {@code T} wherever it is
* deserialized.
*
* @param <T> the type of value supplied.
*/
public interface SerializableSupplier<T> extends Serializable {
T get();
}
/**
* Since the delegate {@link Matcher} is not generally serializable, instead this takes a nullary
* SerializableFunction to return such a matcher.
*/
private static class SerializableMatcherFromSupplier<T> extends BaseMatcher<T>
implements SerializableMatcher<T> {
private SerializableSupplier<Matcher<T>> supplier;
public SerializableMatcherFromSupplier(SerializableSupplier<Matcher<T>> supplier) {
this.supplier = supplier;
}
@Override
public void describeTo(Description description) {
supplier.get().describeTo(description);
}
@Override
public boolean matches(Object item) {
return supplier.get().matches(item);
}
@Override
public void describeMismatch(Object item, Description mismatchDescription) {
supplier.get().describeMismatch(item, mismatchDescription);
}
}
/**
* Wraps any value that can be encoded via a {@link Coder} to make it {@link Serializable}. This
* is not likely to be a good encoding, so should be used only for tests, where data volume is
* small and minor costs are not critical.
*/
private static class SerializableViaCoder<T> implements SerializableSupplier<T> {
/** Cached value that is not serialized. */
@Nullable private transient T value;
/** The bytes of {@link #value} when encoded via {@link #coder}. */
private byte[] encodedValue;
private Coder<T> coder;
public SerializableViaCoder(Coder<T> coder, T value) {
this.coder = coder;
this.value = value;
try {
this.encodedValue = CoderUtils.encodeToByteArray(coder, value);
} catch (CoderException exc) {
throw new RuntimeException("Error serializing via Coder", exc);
}
}
@Override
public T get() {
if (value == null) {
try {
value = CoderUtils.decodeFromByteArray(coder, encodedValue);
} catch (CoderException exc) {
throw new RuntimeException("Error deserializing via Coder", exc);
}
}
return value;
}
}
/**
* Wraps any array with values that can be encoded via a {@link Coder} to make it {@link
* Serializable}. This is not likely to be a good encoding, so should be used only for tests,
* where data volume is small and minor costs are not critical.
*/
private static class SerializableArrayViaCoder<T> implements SerializableSupplier<T[]> {
/** Cached value that is not serialized. */
@Nullable private transient T[] value;
/** The bytes of {@link #value} when encoded via {@link #coder}. */
private byte[] encodedValue;
private Coder<List<T>> coder;
public SerializableArrayViaCoder(Coder<T> elementCoder, T[] value) {
this.coder = ListCoder.of(elementCoder);
this.value = value;
try {
this.encodedValue = CoderUtils.encodeToByteArray(coder, Arrays.asList(value));
} catch (CoderException exc) {
throw UserCodeException.wrap(exc);
}
}
@Override
public T[] get() {
if (value == null) {
try {
@SuppressWarnings("unchecked")
T[] decoded = (T[]) CoderUtils.decodeFromByteArray(coder, encodedValue).toArray();
value = decoded;
} catch (CoderException exc) {
throw new RuntimeException("Error deserializing via Coder", exc);
}
}
return value;
}
}
}