001    /*
002     * Created on Aug 5, 2010
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     * 
007     * http://www.apache.org/licenses/LICENSE-2.0
008     * 
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     * 
013     * Copyright @2010-2011 the original author or authors.
014     */
015    package org.fest.assertions.error;
016    
017    import static java.lang.Integer.toHexString;
018    
019    import static org.fest.util.Arrays.array;
020    import static org.fest.util.Objects.*;
021    import static org.fest.util.ToString.toStringOf;
022    
023    import org.fest.assertions.description.Description;
024    import org.fest.assertions.internal.Failures;
025    import org.fest.util.ComparatorBasedComparisonStrategy;
026    import org.fest.util.ComparisonStrategy;
027    import org.fest.util.StandardComparisonStrategy;
028    import org.fest.util.VisibleForTesting;
029    
030    /**
031     * Creates an <code>{@link AssertionError}</code> indicating that an assertion that verifies that two objects are equal
032     * failed.
033     * <p>
034     * The built {@link AssertionError}'s message differentiates {@link #actual} and {@link #expected} description if their
035     * string representation are the same (e.g. 42 float and 42 double). It also mentions the comparator in case of a custom
036     * comparator is used (instead of equals method).
037     * 
038     * @author Alex Ruiz
039     * @author Yvonne Wang
040     * @author Joel Costigliola
041     */
042    public class ShouldBeEqual implements AssertionErrorFactory {
043    
044      private static final String EXPECTED_BUT_WAS_MESSAGE = "expected:<%s> but was:<%s>";
045      private static final String EXPECTED_BUT_WAS_MESSAGE_USING_COMPARATOR = "Expecting actual:<%s> to be equal to <%s>%s but was not.";
046    
047      private static final Class<?>[] MSG_ARG_TYPES = new Class<?>[] { String.class, String.class, String.class };
048    
049      @VisibleForTesting
050      ConstructorInvoker constructorInvoker = new ConstructorInvoker();
051      @VisibleForTesting
052      MessageFormatter messageFormatter = MessageFormatter.instance();
053      @VisibleForTesting
054      DescriptionFormatter descriptionFormatter = DescriptionFormatter.instance();
055    
056      protected final Object actual;
057      protected final Object expected;
058      private ComparisonStrategy comparisonStrategy;
059    
060      /**
061       * Creates a new <code>{@link ShouldBeEqual}</code>.
062       * @param actual the actual value in the failed assertion.
063       * @param expected the expected value in the failed assertion.
064       * @return the created {@code AssertionErrorFactory}.
065       */
066      public static AssertionErrorFactory shouldBeEqual(Object actual, Object expected) {
067        return new ShouldBeEqual(actual, expected, StandardComparisonStrategy.instance());
068      }
069    
070      /**
071       * Creates a new <code>{@link ShouldBeEqual}</code>.
072       * @param actual the actual value in the failed assertion.
073       * @param expected the expected value in the failed assertion.
074       * @param comparisonStrategy the {@link ComparisonStrategy} used to compare actual with expected.
075       * @return the created {@code AssertionErrorFactory}.
076       */
077      public static AssertionErrorFactory shouldBeEqual(Object actual, Object expected,
078          ComparisonStrategy comparisonStrategy) {
079        return new ShouldBeEqual(actual, expected, comparisonStrategy);
080      }
081      
082      @VisibleForTesting
083      ShouldBeEqual(Object actual, Object expected, ComparisonStrategy comparisonStrategy) {
084        this.actual = actual;
085        this.expected = expected;
086        this.comparisonStrategy = comparisonStrategy;
087      }
088    
089      /**
090       * Creates an <code>{@link AssertionError}</code> indicating that an assertion that verifies that two objects are
091       * equal failed.<br>
092       * The <code>{@link AssertionError}</code> message is built so that it differentiates {@link #actual} and
093       * {@link #expected} description in case their string representation are the same (like 42 float and 42 double).
094       * <p>
095       * If JUnit 4 is in the classpath and the description is standard (no comparator was used and {@link #actual} and
096       * {@link #expected} string representation were differents), this method will instead create a
097       * org.junit.ComparisonFailure that highlights the difference(s) between the expected and actual objects.
098       * </p>
099       * {@link AssertionError} stack trace won't show Fest related elements if {@link Failures} is configured to filter
100       * them (see {@link Failures#setRemoveFestRelatedElementsFromStackTrace(boolean)}).
101       * 
102       * @param description the description of the failed assertion.
103       * @return the created {@code AssertionError}.
104       */
105      public AssertionError newAssertionError(Description description) {
106        if (actualAndExpectedHaveSameStringRepresentation()) {
107          // Example : actual = 42f and expected = 42d gives actual : "42" and expected : "42" and
108          // JUnit 4 manages this case even worst, it will output something like :
109          // "java.lang.String expected:java.lang.String<42.0> but was: java.lang.String<42.0>"
110          // which does not solve the problem and makes things even more confusing since we lost the fact that 42 was a
111          // float or a double, it is then better to built our own description, with the drawback of not using a
112          // ComparisonFailure (which looks nice in eclipse)
113          return Failures.instance().failure(defaultDetailedErrorMessage(description));
114        }
115        // if comparison strategy was based on a custom comparator, we build the assertion error message, the result is
116        // better than the JUnit ComparisonFailure we could build (that would not mention the comparator).
117        if (isJUnitComparisonFailureRelevant()) {
118          // try to build a JUnit ComparisonFailure that offers a nice IDE integration.
119          AssertionError error = comparisonFailure(description);
120          if (error != null) { return error; }
121        }
122        // No JUnit in the classpath => fall back to default error message.
123        return Failures.instance().failure(defaultErrorMessage(description));
124      }
125    
126      /**
127       * Tells {@link #newAssertionError(Description)} if it should try a build a {@link ComparisonFailure}.<br>
128       * Returns <code>true</code> as we try in this class (may not be the case in subclasses).
129       * @return <code>true</code>
130       */
131      private boolean isJUnitComparisonFailureRelevant() {
132        // to add comparator description, we can't rely on JUnit ComparisonFailure since it will ignore it.
133        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) return false;
134        // we don't care to mention the strategy used => trying to build a JUnit comparison failure is relevant.
135        return true;
136      }
137    
138      private boolean actualAndExpectedHaveSameStringRepresentation() {
139        return areEqual(toStringOf(actual), toStringOf(expected));
140      }
141    
142      /**
143       * Builds and returns an error message from description using {@link #expected} and {@link #actual} basic
144       * representation.
145       * @param description the {@link Description} used to build the returned error message
146       * @return the error message from description using {@link #expected} and {@link #actual} basic representation.
147       */
148      private String defaultErrorMessage(Description description) {
149        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy)
150          return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE_USING_COMPARATOR, actual, expected,
151              comparisonStrategy);
152        return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE, expected, actual);
153      }
154    
155      /**
156       * Builds and returns an error message from description using {@link #expectedDetailedToString()} and
157       * {@link #detailedActual()} detailed representation.
158       * @param description the {@link Description} used to build the returned error message
159       * @return the error message from description using {@link #detailedExpected()} and {@link #detailedActual()}
160       *         <b>detailed</b> representation.
161       */
162      private String defaultDetailedErrorMessage(Description description) {
163        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy)
164          return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE_USING_COMPARATOR, detailedActual(),
165              detailedExpected(), comparisonStrategy);
166        return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE, detailedExpected(), detailedActual());
167      }
168    
169      private AssertionError comparisonFailure(Description description) {
170        try {
171          AssertionError comparisonFailure = newComparisonFailure(descriptionFormatter.format(description).trim());
172          Failures.instance().removeFestRelatedElementsFromStackTraceIfNeeded(comparisonFailure);
173          return comparisonFailure;
174        } catch (Throwable e) {
175          return null;
176        }
177      }
178    
179      private AssertionError newComparisonFailure(String description) throws Exception {
180        Object o = constructorInvoker.newInstance("org.junit.ComparisonFailure", MSG_ARG_TYPES, msgArgs(description));
181        if (o instanceof AssertionError) return (AssertionError) o;
182        return null;
183      }
184    
185      private Object[] msgArgs(String description) {
186        return array(description, toStringOf(expected), toStringOf(actual));
187      }
188    
189      private static String detailedToStringOf(Object obj) {
190        return toStringOf(obj) + " (" + obj.getClass().getSimpleName() + "@" + toHexString(obj.hashCode()) + ")";
191      }
192    
193      private String detailedActual() {
194        return detailedToStringOf(actual);
195      }
196    
197      private String detailedExpected() {
198        return detailedToStringOf(expected);
199      }
200    
201      @Override
202      public boolean equals(Object o) {
203        if (this == o) return true;
204        if (o == null) return false;
205        if (getClass() != o.getClass()) return false;
206        ShouldBeEqual other = (ShouldBeEqual) o;
207        if (!areEqual(actual, other.actual)) return false;
208        return areEqual(expected, other.expected);
209      }
210    
211      @Override
212      public int hashCode() {
213        int result = 1;
214        result = HASH_CODE_PRIME * result + hashCodeFor(actual);
215        result = HASH_CODE_PRIME * result + hashCodeFor(expected);
216        return result;
217      }
218    }