package net.thucydides.core.matchers;
import ch.lambdaj.Lambda;
import ch.lambdaj.function.convert.Converter;
import com.google.common.collect.ImmutableList;
import org.apache.commons.collections.ListUtils;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static ch.lambdaj.Lambda.*;
import static org.hamcrest.Matchers.*;
public class BeanMatcherAsserts {
private static final String NEW_LINE = System.getProperty("line.separator");
public static <T> boolean matches(List<T> elements, BeanMatcher... matchers) {
List<T> filteredElements = filterElements(elements, matchers);
return apply(filteredElements, collectionMatchersIn(matchers));
}
private static <T> boolean apply(final List<T> elements, final List<BeanCollectionMatcher> matchers) {
List<BeanCollectionMatcher> collectionMatchers = addEmptyTestIfNoCountChecksArePresentTo(matchers);
for (BeanCollectionMatcher matcher : collectionMatchers) {
if (!matcher.matches(elements)) {
return false;
}
}
return true;
}
private static List<BeanCollectionMatcher> addEmptyTestIfNoCountChecksArePresentTo(final List<BeanCollectionMatcher> matchers) {
if (thereIsACardinalityConstraintSpecifiedInThe(matchers)) {
return matchers;
} else {
return mustContainAtLeastOneItemMatching(matchers);
}
}
private static List mustContainAtLeastOneItemMatching(List<BeanCollectionMatcher> matchers) {
return ListUtils.union(matchers, Arrays.asList(BeanMatchers.the_count(is(not(0)))));
}
private static boolean thereIsACardinalityConstraintSpecifiedInThe(List<BeanCollectionMatcher> matchers) {
for(BeanCollectionMatcher matcher : matchers) {
if (matcher instanceof BeanCountMatcher) {
return true;
}
}
return false;
}
private static List<BeanCollectionMatcher> collectionMatchersIn(final BeanMatcher[] matchers) {
List<BeanMatcher> compatibleMatchers = Lambda.filter(instanceOf(BeanCollectionMatcher.class), matchers);
return convert(compatibleMatchers, toBeanCollectionMatchers());
}
private static Converter<BeanMatcher, BeanCollectionMatcher> toBeanCollectionMatchers() {
return new Converter<BeanMatcher, BeanCollectionMatcher>() {
@Override
public BeanCollectionMatcher convert(BeanMatcher from) {
return (BeanCollectionMatcher) from;
}
};
}
public static <T> List<T> filterElements(final List<T> elements, final BeanMatcher... matchers) {
List<T> filteredItems = ImmutableList.copyOf(elements);
for(BeanFieldMatcher matcher : propertyMatchersIn(matchers)) {
filteredItems = filter(matcher.getMatcher(), filteredItems);
}
return filteredItems;
}
private static List<BeanFieldMatcher> propertyMatchersIn(BeanMatcher[] matchers) {
List<BeanMatcher> compatibleMatchers = filter(instanceOf(BeanFieldMatcher.class), matchers);
return convert(compatibleMatchers, toBeanFieldMatchers());
}
private static Converter<BeanMatcher, BeanFieldMatcher> toBeanFieldMatchers() {
return new Converter<BeanMatcher, BeanFieldMatcher>() {
@Override
public BeanFieldMatcher convert(BeanMatcher from) {
return (BeanFieldMatcher) from;
}
};
}
public static <T> void shouldMatch(List<T> items, BeanMatcher... matchers) {
if (!matches(items, matchers)) {
throw new AssertionError("Failed to find matching elements for " + join(matchers)
+ NEW_LINE
+"Elements where " + join(items));
}
}
public static <T> void shouldMatch(T bean, BeanMatcher... matchers) {
if (!matches(bean, matchers)) {
throw new AssertionError("Expected " + Arrays.toString(matchers) +
" but was " + descriptionOf(bean));
}
}
private static String descriptionOf(Object bean) {
if (isAMap(bean)) {
return mapDescription((Map<String, ? extends Object>) bean);
} else {
return beanDescription(bean);
}
}
private static String beanDescription(Object bean) {
List<String> propertyTerms = new ArrayList<String>();
try {
for(PropertyDescriptor descriptor : propertiesOf(bean)) {
Method getter = descriptor.getReadMethod();
if (getter != null) {
propertyTerms.add(propertyValueOf(descriptor.getDisplayName(), getter.invoke(bean).toString()));
}
}
return join(propertyTerms);
} catch (Throwable e) {
throw new IllegalArgumentException("Could not read bean properties", e);
}
}
private static String mapDescription(Map<String, ? extends Object> map) {
List<String> propertyTerms = new ArrayList<String>();
for (String key : map.keySet()) {
propertyTerms.add(propertyValueOf(key, map.get(key).toString()));
}
return join(propertyTerms);
}
private static boolean isAMap(Object bean) {
return Map.class.isAssignableFrom(bean.getClass());
}
public static <T> boolean matches(T bean, BeanMatcher... matchers) {
return matches(Arrays.asList(bean), matchers);
}
private static String propertyValueOf(String propertyName, String value) {
return propertyName + " = '" + value + "'";
}
private static <T> PropertyDescriptor[] propertiesOf(T bean) throws IntrospectionException {
return Introspector.getBeanInfo(bean.getClass(), Object.class)
.getPropertyDescriptors();
}
}