1/*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.testing;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import com.google.common.annotations.GwtCompatible;
22import com.google.common.collect.ImmutableList;
23import com.google.common.collect.Lists;
24
25import junit.framework.AssertionFailedError;
26
27import java.util.List;
28
29/**
30 * Tests a collection of objects according to the rules specified in a
31 * {@link RelationshipAssertion}.
32 *
33 * @author Gregory Kick
34 */
35@GwtCompatible
36final class RelationshipTester<T> {
37  private final List<ImmutableList<T>> groups = Lists.newArrayList();
38  private final RelationshipAssertion<T> assertion;
39
40  RelationshipTester(RelationshipAssertion<T> assertion) {
41    this.assertion = checkNotNull(assertion);
42  }
43
44  public RelationshipTester<T> addRelatedGroup(Iterable<? extends T> group) {
45    groups.add(ImmutableList.copyOf(group));
46    return this;
47  }
48
49  public void test() {
50    for (int groupNumber = 0; groupNumber < groups.size(); groupNumber++) {
51      ImmutableList<T> group = groups.get(groupNumber);
52      for (int itemNumber = 0; itemNumber < group.size(); itemNumber++) {
53        // check related items in same group
54        for (int relatedItemNumber = 0; relatedItemNumber < group.size(); relatedItemNumber++) {
55          if (itemNumber != relatedItemNumber) {
56            assertRelated(groupNumber, itemNumber, relatedItemNumber);
57          }
58        }
59        // check unrelated items in all other groups
60        for (int unrelatedGroupNumber = 0; unrelatedGroupNumber < groups.size();
61            unrelatedGroupNumber++) {
62          if (groupNumber != unrelatedGroupNumber) {
63            ImmutableList<T> unrelatedGroup = groups.get(unrelatedGroupNumber);
64            for (int unrelatedItemNumber = 0; unrelatedItemNumber < unrelatedGroup.size();
65                unrelatedItemNumber++) {
66              assertUnrelated(groupNumber, itemNumber, unrelatedGroupNumber, unrelatedItemNumber);
67            }
68          }
69        }
70      }
71    }
72  }
73
74  private void assertRelated(int groupNumber, int itemNumber, int relatedItemNumber) {
75    ImmutableList<T> group = groups.get(groupNumber);
76    T item = group.get(itemNumber);
77    T related = group.get(relatedItemNumber);
78    try {
79      assertion.assertRelated(item, related);
80    } catch (AssertionFailedError e) {
81      // TODO(gak): special handling for ComparisonFailure?
82      throw new AssertionFailedError(e.getMessage()
83          .replace("$ITEM", itemString(item, groupNumber, itemNumber))
84          .replace("$RELATED", itemString(related, groupNumber, relatedItemNumber)));
85    }
86  }
87
88  private void assertUnrelated(int groupNumber, int itemNumber, int unrelatedGroupNumber,
89      int unrelatedItemNumber) {
90    T item = groups.get(groupNumber).get(itemNumber);
91    T unrelated = groups.get(unrelatedGroupNumber).get(unrelatedItemNumber);
92    try {
93      assertion.assertUnrelated(item, unrelated);
94    } catch (AssertionFailedError e) {
95      // TODO(gak): special handling for ComparisonFailure?
96      throw new AssertionFailedError(e.getMessage()
97          .replace("$ITEM", itemString(item, groupNumber, itemNumber))
98          .replace("$UNRELATED", itemString(unrelated, unrelatedGroupNumber, unrelatedItemNumber)));
99    }
100  }
101
102  private static String itemString(Object item, int groupNumber, int itemNumber) {
103    return new StringBuilder()
104        .append(item)
105        .append(" [group ")
106        .append(groupNumber + 1)
107        .append(", item ")
108        .append(itemNumber + 1)
109        .append(']')
110        .toString();
111  }
112
113  /**
114   * A strategy for testing the relationship between objects.  Methods are expected to throw
115   * {@link AssertionFailedError} whenever the relationship is violated.
116   *
117   * <p>As a convenience, any occurrence of {@code $ITEM}, {@code $RELATED} or {@code $UNRELATED} in
118   * the error message will be replaced with a string that combines the {@link Object#toString()},
119   * item number and group number of the respective item.
120   *
121   */
122  interface RelationshipAssertion<T> {
123    void assertRelated(T item, T related);
124
125    void assertUnrelated(T item, T unrelated);
126  }
127}
128