OutcomesTable.java

  1. package org.jbehave.core.model;

  2. import java.lang.reflect.Type;
  3. import java.util.ArrayList;
  4. import java.util.Date;
  5. import java.util.HashMap;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. import java.util.Map;

  9. import org.apache.commons.lang3.builder.ToStringBuilder;
  10. import org.apache.commons.lang3.builder.ToStringStyle;
  11. import org.hamcrest.Matcher;
  12. import org.jbehave.core.configuration.Keywords;
  13. import org.jbehave.core.failures.UUIDExceptionWrapper;
  14. import org.jbehave.core.i18n.LocalizedKeywords;

  15. /**
  16.  * Represents a tabular structure that holds {@link Outcome}s to be verified by invoking method {@link #verify()}. If
  17.  * verification fails an {@link OutcomesFailed} exception is thrown.
  18.  * <p>The Outcomes Tables allows the specification of {@link Keywords} for the outcome fields, as well as rendering
  19.  * formats for different types. The default formats include:
  20.  * <ul>
  21.  *     <li>Date: "EEE MMM dd hh:mm:ss zzz yyyy"</li>
  22.  *     <li>Number: "0.###"</li>
  23.  *     <li>Boolean: "yes,no"</li>
  24.  * </ul>
  25.  * These formats can be overridden as well as new ones added. The formats can be retrieved via methods
  26.  * {@link #getFormat(Type)} and {@link #getFormat(String)}.</p>
  27.  */
  28. public class OutcomesTable {

  29.     private static final String NEWLINE = "\n";
  30.     private static final String HEADER_SEPARATOR = "|";
  31.     private static final String VALUE_SEPARATOR = "|";

  32.     private final Keywords keywords;
  33.     private final Map<Type,String> formats;
  34.     private final List<Outcome<?>> outcomes = new ArrayList<>();
  35.     private final List<Outcome<?>> failedOutcomes = new ArrayList<>();
  36.     private UUIDExceptionWrapper failureCause;

  37.     public OutcomesTable() {
  38.         this(new LocalizedKeywords());
  39.     }

  40.     public OutcomesTable(Keywords keywords) {
  41.         this(keywords, defaultFormats());
  42.     }

  43.     public OutcomesTable(Map<Type, String> formats) {
  44.         this(new LocalizedKeywords(), formats);
  45.     }

  46.     public OutcomesTable(Keywords keywords, Map<Type, String> formats) {
  47.         this.keywords = keywords;
  48.         this.formats = mergeWithDefaults(formats);
  49.     }

  50.     /**
  51.      * Creates outcomes table using the specified keywords and date format
  52.      *
  53.      * @deprecated Use {@link #OutcomesTable(Keywords, Map)}
  54.      */
  55.     @Deprecated
  56.     public OutcomesTable(Keywords keywords, String dateFormat) {
  57.         this(keywords, mergeWithDefaults(Date.class, dateFormat));
  58.     }

  59.     public <T> void addOutcome(String description, T value, Matcher<T> matcher) {
  60.         outcomes.add(new Outcome<>(description, value, matcher));
  61.     }

  62.     public void verify() {
  63.         boolean failed = false;
  64.         failedOutcomes.clear();
  65.         for (Outcome<?> outcome : outcomes) {
  66.             if (!outcome.isVerified()) {
  67.                 failedOutcomes.add(outcome);
  68.                 failed = true;
  69.                 break;
  70.             }
  71.         }
  72.         if (failed) {
  73.             failureCause = new UUIDExceptionWrapper(new OutcomesFailed(this));
  74.             throw failureCause;
  75.         }
  76.     }

  77.     public UUIDExceptionWrapper failureCause() {
  78.         return failureCause;
  79.     }

  80.     public List<Outcome<?>> getOutcomes() {
  81.         return outcomes;
  82.     }

  83.     public List<Outcome<?>> getFailedOutcomes() {
  84.         return failedOutcomes;
  85.     }

  86.     public List<String> getOutcomeFields() {
  87.         return keywords.outcomeFields();
  88.     }

  89.     public Map<Type, String> getFormats() {
  90.         return formats;
  91.     }

  92.     public String getFormat(Type type) {
  93.         return formats.get(type);
  94.     }

  95.     public String getFormat(String typeName) {
  96.         try {
  97.             return getFormat(Class.forName(typeName));
  98.         } catch (ClassNotFoundException e) {
  99.             throw new FormatTypeInvalid(typeName, e);
  100.         }
  101.     }

  102.     /**
  103.      * Provides used date format
  104.      *
  105.      * @deprecated Use {@link #getFormat(Type)}
  106.      */
  107.     @Deprecated
  108.     public String getDateFormat() {
  109.         return getFormat(Date.class);
  110.     }

  111.     public String asString() {
  112.         StringBuilder sb = new StringBuilder();
  113.         for (Iterator<String> iterator = getOutcomeFields().iterator(); iterator.hasNext();) {
  114.             sb.append(HEADER_SEPARATOR).append(iterator.next());
  115.             if (!iterator.hasNext()) {
  116.                 sb.append(HEADER_SEPARATOR).append(NEWLINE);
  117.             }
  118.         }
  119.         for (Outcome<?> outcome : outcomes) {
  120.             sb.append(VALUE_SEPARATOR).append(outcome.getDescription()).append(VALUE_SEPARATOR).append(
  121.                     outcome.getValue()).append(VALUE_SEPARATOR).append(outcome.getMatcher()).append(VALUE_SEPARATOR)
  122.                     .append(outcome.isVerified()).append(VALUE_SEPARATOR).append(NEWLINE);
  123.         }
  124.         return sb.toString();
  125.     }

  126.     @Override
  127.     public String toString() {
  128.         return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  129.     }

  130.     private static Map<Type,String> defaultFormats() {
  131.         Map<Type,String> map = new HashMap<>();
  132.         map.put(Date.class, "EEE MMM dd hh:mm:ss zzz yyyy");
  133.         map.put(Number.class, "0.###");
  134.         map.put(Boolean.class, "yes,no");
  135.         return map;
  136.     }

  137.     private static Map<Type,String> mergeWithDefaults(Type type, String format) {
  138.         Map<Type,String> map = defaultFormats();
  139.         map.put(type, format);
  140.         return map;
  141.     }

  142.     private Map<Type, String> mergeWithDefaults(Map<Type, String> formats) {
  143.         Map<Type,String> map = defaultFormats();
  144.         map.putAll(formats);
  145.         return map;
  146.     }

  147.     public static class Outcome<T> {

  148.         private final String description;
  149.         private final T value;
  150.         private final Matcher<T> matcher;
  151.         private final boolean verified;

  152.         public Outcome(String description, T value, Matcher<T> matcher) {
  153.             this.description = description;
  154.             this.value = value;
  155.             this.matcher = matcher;
  156.             this.verified = matcher.matches(value);
  157.         }

  158.         public String getDescription() {
  159.             return description;
  160.         }

  161.         public T getValue() {
  162.             return value;
  163.         }

  164.         public Matcher<T> getMatcher() {
  165.             return matcher;
  166.         }

  167.         public boolean isVerified() {
  168.             return verified;
  169.         }

  170.         @Override
  171.         public String toString() {
  172.             return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  173.         }
  174.     }

  175.     @SuppressWarnings("serial")
  176.     public static class OutcomesFailed extends UUIDExceptionWrapper {
  177.         private transient OutcomesTable outcomes;

  178.         public OutcomesFailed(OutcomesTable outcomes) {
  179.             this.outcomes = outcomes;
  180.         }

  181.         public OutcomesTable outcomesTable() {
  182.             return outcomes;
  183.         }

  184.     }

  185.     public static class FormatTypeInvalid extends RuntimeException {
  186.         public FormatTypeInvalid(String type, Throwable e) {
  187.             super(type, e);
  188.         }
  189.     }
  190. }