OutcomesTable.java
package org.jbehave.core.model;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hamcrest.Matcher;
import org.jbehave.core.configuration.Keywords;
import org.jbehave.core.failures.UUIDExceptionWrapper;
import org.jbehave.core.i18n.LocalizedKeywords;
/**
* Represents a tabular structure that holds {@link Outcome}s to be verified by invoking method {@link #verify()}. If
* verification fails an {@link OutcomesFailed} exception is thrown.
* <p>The Outcomes Tables allows the specification of {@link Keywords} for the outcome fields, as well as rendering
* formats for different types. The default formats include:
* <ul>
* <li>Date: "EEE MMM dd hh:mm:ss zzz yyyy"</li>
* <li>Number: "0.###"</li>
* <li>Boolean: "yes,no"</li>
* </ul>
* These formats can be overridden as well as new ones added. The formats can be retrieved via methods
* {@link #getFormat(Type)} and {@link #getFormat(String)}.</p>
*/
public class OutcomesTable {
private static final String NEWLINE = "\n";
private static final String HEADER_SEPARATOR = "|";
private static final String VALUE_SEPARATOR = "|";
private final Keywords keywords;
private final Map<Type,String> formats;
private final List<Outcome<?>> outcomes = new ArrayList<>();
private final List<Outcome<?>> failedOutcomes = new ArrayList<>();
private UUIDExceptionWrapper failureCause;
public OutcomesTable() {
this(new LocalizedKeywords());
}
public OutcomesTable(Keywords keywords) {
this(keywords, defaultFormats());
}
public OutcomesTable(Map<Type, String> formats) {
this(new LocalizedKeywords(), formats);
}
public OutcomesTable(Keywords keywords, Map<Type, String> formats) {
this.keywords = keywords;
this.formats = mergeWithDefaults(formats);
}
/**
* Creates outcomes table using the specified keywords and date format
*
* @deprecated Use {@link #OutcomesTable(Keywords, Map)}
*/
@Deprecated
public OutcomesTable(Keywords keywords, String dateFormat) {
this(keywords, mergeWithDefaults(Date.class, dateFormat));
}
public <T> void addOutcome(String description, T value, Matcher<T> matcher) {
outcomes.add(new Outcome<>(description, value, matcher));
}
public void verify() {
boolean failed = false;
failedOutcomes.clear();
for (Outcome<?> outcome : outcomes) {
if (!outcome.isVerified()) {
failedOutcomes.add(outcome);
failed = true;
break;
}
}
if (failed) {
failureCause = new UUIDExceptionWrapper(new OutcomesFailed(this));
throw failureCause;
}
}
public UUIDExceptionWrapper failureCause() {
return failureCause;
}
public List<Outcome<?>> getOutcomes() {
return outcomes;
}
public List<Outcome<?>> getFailedOutcomes() {
return failedOutcomes;
}
public List<String> getOutcomeFields() {
return keywords.outcomeFields();
}
public Map<Type, String> getFormats() {
return formats;
}
public String getFormat(Type type) {
return formats.get(type);
}
public String getFormat(String typeName) {
try {
return getFormat(Class.forName(typeName));
} catch (ClassNotFoundException e) {
throw new FormatTypeInvalid(typeName, e);
}
}
/**
* Provides used date format
*
* @deprecated Use {@link #getFormat(Type)}
*/
@Deprecated
public String getDateFormat() {
return getFormat(Date.class);
}
public String asString() {
StringBuilder sb = new StringBuilder();
for (Iterator<String> iterator = getOutcomeFields().iterator(); iterator.hasNext();) {
sb.append(HEADER_SEPARATOR).append(iterator.next());
if (!iterator.hasNext()) {
sb.append(HEADER_SEPARATOR).append(NEWLINE);
}
}
for (Outcome<?> outcome : outcomes) {
sb.append(VALUE_SEPARATOR).append(outcome.getDescription()).append(VALUE_SEPARATOR).append(
outcome.getValue()).append(VALUE_SEPARATOR).append(outcome.getMatcher()).append(VALUE_SEPARATOR)
.append(outcome.isVerified()).append(VALUE_SEPARATOR).append(NEWLINE);
}
return sb.toString();
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
private static Map<Type,String> defaultFormats() {
Map<Type,String> map = new HashMap<>();
map.put(Date.class, "EEE MMM dd hh:mm:ss zzz yyyy");
map.put(Number.class, "0.###");
map.put(Boolean.class, "yes,no");
return map;
}
private static Map<Type,String> mergeWithDefaults(Type type, String format) {
Map<Type,String> map = defaultFormats();
map.put(type, format);
return map;
}
private Map<Type, String> mergeWithDefaults(Map<Type, String> formats) {
Map<Type,String> map = defaultFormats();
map.putAll(formats);
return map;
}
public static class Outcome<T> {
private final String description;
private final T value;
private final Matcher<T> matcher;
private final boolean verified;
public Outcome(String description, T value, Matcher<T> matcher) {
this.description = description;
this.value = value;
this.matcher = matcher;
this.verified = matcher.matches(value);
}
public String getDescription() {
return description;
}
public T getValue() {
return value;
}
public Matcher<T> getMatcher() {
return matcher;
}
public boolean isVerified() {
return verified;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
@SuppressWarnings("serial")
public static class OutcomesFailed extends UUIDExceptionWrapper {
private transient OutcomesTable outcomes;
public OutcomesFailed(OutcomesTable outcomes) {
this.outcomes = outcomes;
}
public OutcomesTable outcomesTable() {
return outcomes;
}
}
public static class FormatTypeInvalid extends RuntimeException {
public FormatTypeInvalid(String type, Throwable e) {
super(type, e);
}
}
}