org.itsallcode.matcher.config.MatcherConfig Maven / Gradle / Ivy
package org.itsallcode.matcher.config;
import static java.util.stream.Collectors.toList;
import java.util.*;
import java.util.function.Function;
import java.util.stream.StreamSupport;
import org.hamcrest.*;
/**
* Configuration for a {@link ConfigurableMatcher}.
*
* @param type of the object that is handled by the matcher
*/
public final class MatcherConfig {
private final T expected;
private final List> propertyConfigs;
private MatcherConfig(final T expected, final List> propertyConfigs) {
this.expected = expected;
this.propertyConfigs = Collections.unmodifiableList(propertyConfigs);
}
T getExpected() {
return this.expected;
}
/**
* Get the property configurations.
*
* @return property configurations
*/
@SuppressWarnings("unchecked")
public List> getPropertyConfigs() {
return this.propertyConfigs.stream() //
.map(c -> (PropertyConfig) c) //
.collect(toList());
}
/**
* Create a new {@link Builder} for the given expected model.
*
* @param The type of the model to compare.
* @param expected the expected model
* @return new builder
*/
public static Builder builder(final B expected) {
return new Builder<>(expected);
}
/**
* Builder for {@link MatcherConfig}.
*
* @param The type of the model to compare.
*/
public static final class Builder {
private final B expected;
private final List> properties = new ArrayList<>();
private Builder(final B expected) {
this.expected = Objects.requireNonNull(expected);
}
/**
* Add a property that can be compared with {@link Matchers#equalTo(Object)}.
*
* @param propertyName name of the property.
* @param propertyAccessor the accessor function for retrieving the property
* value.
* @param the type of the property.
* @return the builder itself for fluent programming style.
*/
public
Builder addEqualsProperty(final String propertyName, final Function propertyAccessor) {
return addProperty(propertyName, propertyAccessor, Matchers::equalTo);
}
/**
* Add a property that can be compared with {@link Matchers#equalTo(Object)}.
*
* @param propertyName name of the property.
* @param propertyAccessor the accessor function for retrieving the property
* value.
* @param matcherBuilder a function for creating the matcher.
* @param
the type of the property.
* @return the builder itself for fluent programming style.
*/
public
Builder addProperty(final String propertyName, final Function propertyAccessor,
final Function
> matcherBuilder) {
final Matcher
matcher = createMatcher(propertyAccessor, matcherBuilder);
return addPropertyInternal(propertyName, matcher, propertyAccessor);
}
@SuppressWarnings("unchecked")
private
Matcher
createMatcher(final Function propertyAccessor,
final Function
> matcherBuilder) {
final P expectedValue = propertyAccessor.apply(this.expected);
if (expectedValue == null) {
return (Matcher
) Matchers.nullValue();
}
return matcherBuilder.apply(expectedValue);
}
/**
* Add a property of type {@link Iterable} where the element order is relevant.
*
* @param propertyName name of the property.
* @param propertyAccessor the accessor function for retrieving the property
* value.
* @param matcherBuilder a function for creating the matcher for the iterable
* elements.
* @param
the type of the property.
* @return the builder itself for fluent programming style.
*/
public
Builder addIterableProperty(final String propertyName,
final Function> propertyAccessor,
final Function
> matcherBuilder) {
final Iterable extends P> expectedPropertyValue = propertyAccessor.apply(this.expected);
final Matcher> listMatcher = createListMatcher(matcherBuilder, expectedPropertyValue);
return addPropertyInternal(propertyName, listMatcher, propertyAccessor);
}
private Matcher> createListMatcher(final Function> matcherBuilder,
final Iterable extends P> expectedPropertyValue) {
if (expectedPropertyValue == null) {
return createNullIterableMatcher();
}
if (!expectedPropertyValue.iterator().hasNext()) {
return Matchers.
emptyIterable();
}
final List> matchers = StreamSupport.stream(expectedPropertyValue.spliterator(), false)
.map(matcherBuilder) //
.collect(toList());
return Matchers.contains(matchers);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Matcher> createNullIterableMatcher() {
return new NullIterableMatcher();
}
private Builder addPropertyInternal(final String propertyName, final Matcher
matcher,
final Function propertyAccessor) {
this.properties.add(new PropertyConfig<>(propertyName, matcher, propertyAccessor));
return this;
}
/**
* Build a new {@link MatcherConfig}.
*
* @return the new {@link MatcherConfig}.
*/
public MatcherConfig build() {
if (this.properties.isEmpty()) {
throw new IllegalArgumentException("Failed to build MatcherConfig: Class "
+ this.expected.getClass().getName() + " has no properties.");
}
return new MatcherConfig<>(this.expected, new ArrayList<>(this.properties));
}
}
private static class NullIterableMatcher extends BaseMatcher> {
@Override
public boolean matches(final Object item) {
return item == null;
}
@Override
public void describeTo(final Description description) {
description.appendText("null");
}
}
}