Home | History | Annotate | Download | only in collection
      1 package org.hamcrest.collection;
      2 
      3 import org.hamcrest.Description;
      4 import org.hamcrest.Matcher;
      5 import org.hamcrest.TypeSafeDiagnosingMatcher;
      6 
      7 import java.util.ArrayList;
      8 import java.util.Arrays;
      9 import java.util.Collection;
     10 import java.util.List;
     11 
     12 import static org.hamcrest.core.IsEqual.equalTo;
     13 
     14 public class IsIterableContainingInAnyOrder<T> extends TypeSafeDiagnosingMatcher<Iterable<? extends T>> {
     15     private final Collection<Matcher<? super T>> matchers;
     16 
     17     public IsIterableContainingInAnyOrder(Collection<Matcher<? super T>> matchers) {
     18         this.matchers = matchers;
     19     }
     20 
     21     @Override
     22     protected boolean matchesSafely(Iterable<? extends T> items, Description mismatchDescription) {
     23       final Matching<T> matching = new Matching<T>(matchers, mismatchDescription);
     24       for (T item : items) {
     25         if (! matching.matches(item)) {
     26           return false;
     27         }
     28       }
     29 
     30       return matching.isFinished(items);
     31     }
     32 
     33     @Override
     34     public void describeTo(Description description) {
     35       description.appendText("iterable with items ")
     36           .appendList("[", ", ", "]", matchers)
     37           .appendText(" in any order");
     38     }
     39 
     40     private static class Matching<S> {
     41       private final Collection<Matcher<? super S>> matchers;
     42       private final Description mismatchDescription;
     43 
     44       public Matching(Collection<Matcher<? super S>> matchers, Description mismatchDescription) {
     45         this.matchers = new ArrayList<Matcher<? super S>>(matchers);
     46         this.mismatchDescription = mismatchDescription;
     47       }
     48 
     49       public boolean matches(S item) {
     50         if (matchers.isEmpty()) {
     51           mismatchDescription.appendText("no match for: ").appendValue(item);
     52           return false;
     53         }
     54         return isMatched(item);
     55       }
     56 
     57       public boolean isFinished(Iterable<? extends S> items) {
     58         if (matchers.isEmpty()) {
     59           return true;
     60         }
     61         mismatchDescription
     62           .appendText("no item matches: ").appendList("", ", ", "", matchers)
     63           .appendText(" in ").appendValueList("[", ", ", "]", items);
     64         return false;
     65       }
     66 
     67       private boolean isMatched(S item) {
     68         for (Matcher<? super S>  matcher : matchers) {
     69           if (matcher.matches(item)) {
     70             matchers.remove(matcher);
     71             return true;
     72           }
     73         }
     74         mismatchDescription.appendText("not matched: ").appendValue(item);
     75         return false;
     76       }
     77     }
     78 
     79     /**
     80      * <p>
     81      * Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
     82      * the examined {@link Iterable} yields a series of items, each satisfying one matcher anywhere
     83      * in the specified matchers.  For a positive match, the examined iterable must be of the same
     84      * length as the number of specified matchers.
     85      * </p>
     86      * <p>
     87      * N.B. each of the specified matchers will only be used once during a given examination, so be
     88      * careful when specifying matchers that may be satisfied by more than one entry in an examined
     89      * iterable.
     90      * </p>
     91      * <p>
     92      * For example:
     93      * </p>
     94      * <pre>assertThat(Arrays.asList("foo", "bar"), containsInAnyOrder(equalTo("bar"), equalTo("foo")))</pre>
     95      *
     96      * @param itemMatchers
     97      *     a list of matchers, each of which must be satisfied by an item provided by an examined {@link Iterable}
     98      */
     99     public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(Matcher<? super T>... itemMatchers) {
    100         return containsInAnyOrder((List) Arrays.asList(itemMatchers));
    101     }
    102 
    103     /**
    104      * <p>
    105      * Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
    106      * the examined {@link Iterable} yields a series of items, each logically equal to one item
    107      * anywhere in the specified items. For a positive match, the examined iterable
    108      * must be of the same length as the number of specified items.
    109      * </p>
    110      * <p>
    111      * N.B. each of the specified items will only be used once during a given examination, so be
    112      * careful when specifying items that may be equal to more than one entry in an examined
    113      * iterable.
    114      * </p>
    115      * <p>
    116      * For example:
    117      * </p>
    118      * <pre>assertThat(Arrays.asList("foo", "bar"), containsInAnyOrder("bar", "foo"))</pre>
    119      *
    120      * @param items
    121      *     the items that must equal the items provided by an examined {@link Iterable} in any order
    122      */
    123     public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(T... items) {
    124         List<Matcher<? super T>> matchers = new ArrayList<Matcher<? super T>>();
    125         for (T item : items) {
    126             matchers.add(equalTo(item));
    127         }
    128 
    129         return new IsIterableContainingInAnyOrder<T>(matchers);
    130     }
    131 
    132     /**
    133      * <p>
    134      * Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
    135      * the examined {@link Iterable} yields a series of items, each satisfying one matcher anywhere
    136      * in the specified collection of matchers.  For a positive match, the examined iterable
    137      * must be of the same length as the specified collection of matchers.
    138      * </p>
    139      * <p>
    140      * N.B. each matcher in the specified collection will only be used once during a given
    141      * examination, so be careful when specifying matchers that may be satisfied by more than
    142      * one entry in an examined iterable.
    143      * </p>
    144      * <p>For example:</p>
    145      * <pre>assertThat(Arrays.asList("foo", "bar"), containsInAnyOrder(Arrays.asList(equalTo("bar"), equalTo("foo"))))</pre>
    146      *
    147      * @param itemMatchers
    148      *     a list of matchers, each of which must be satisfied by an item provided by an examined {@link Iterable}
    149      */
    150     public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(Collection<Matcher<? super T>> itemMatchers) {
    151         return new IsIterableContainingInAnyOrder<T>(itemMatchers);
    152     }
    153 }
    154 
    155