001package org.junit;
002
003/**
004 * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. Create and throw
005 * a <code>ComparisonFailure</code> manually if you want to show users the difference between two complex
006 * strings.
007 *
008 * Inspired by a patch from Alex Chaffee (alex@purpletech.com)
009 *
010 * @since 4.0
011 */
012public class ComparisonFailure extends AssertionError {
013    /**
014     * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened.
015     *
016     * @see ComparisonCompactor
017     */
018    private static final int MAX_CONTEXT_LENGTH = 20;
019    private static final long serialVersionUID = 1L;
020
021    private String fExpected;
022    private String fActual;
023
024    /**
025     * Constructs a comparison failure.
026     *
027     * @param message the identifying message or null
028     * @param expected the expected string value
029     * @param actual the actual string value
030     */
031    public ComparisonFailure(String message, String expected, String actual) {
032        super(message);
033        fExpected = expected;
034        fActual = actual;
035    }
036
037    /**
038     * Returns "..." in place of common prefix and "..." in
039     * place of common suffix between expected and actual.
040     *
041     * @see Throwable#getMessage()
042     */
043    @Override
044    public String getMessage() {
045        return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
046    }
047
048    /**
049     * Returns the actual string value
050     *
051     * @return the actual string value
052     */
053    public String getActual() {
054        return fActual;
055    }
056
057    /**
058     * Returns the expected string value
059     *
060     * @return the expected string value
061     */
062    public String getExpected() {
063        return fExpected;
064    }
065
066    private static class ComparisonCompactor {
067        private static final String ELLIPSIS = "...";
068        private static final String DELTA_END = "]";
069        private static final String DELTA_START = "[";
070
071        /**
072         * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code>
073         * is exceeded, the Strings are shortened
074         */
075        private int fContextLength;
076        private String fExpected;
077        private String fActual;
078        private int fPrefix;
079        private int fSuffix;
080
081        /**
082         * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength
083         * is exceeded, the Strings are shortened
084         * @param expected the expected string value
085         * @param actual the actual string value
086         */
087        public ComparisonCompactor(int contextLength, String expected, String actual) {
088            fContextLength = contextLength;
089            fExpected = expected;
090            fActual = actual;
091        }
092
093        private String compact(String message) {
094            if (fExpected == null || fActual == null || areStringsEqual()) {
095                return Assert.format(message, fExpected, fActual);
096            }
097
098            findCommonPrefix();
099            findCommonSuffix();
100            String expected = compactString(fExpected);
101            String actual = compactString(fActual);
102            return Assert.format(message, expected, actual);
103        }
104
105        private String compactString(String source) {
106            String result = DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
107            if (fPrefix > 0) {
108                result = computeCommonPrefix() + result;
109            }
110            if (fSuffix > 0) {
111                result = result + computeCommonSuffix();
112            }
113            return result;
114        }
115
116        private void findCommonPrefix() {
117            fPrefix = 0;
118            int end = Math.min(fExpected.length(), fActual.length());
119            for (; fPrefix < end; fPrefix++) {
120                if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) {
121                    break;
122                }
123            }
124        }
125
126        private void findCommonSuffix() {
127            int expectedSuffix = fExpected.length() - 1;
128            int actualSuffix = fActual.length() - 1;
129            for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) {
130                if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) {
131                    break;
132                }
133            }
134            fSuffix = fExpected.length() - expectedSuffix;
135        }
136
137        private String computeCommonPrefix() {
138            return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
139        }
140
141        private String computeCommonSuffix() {
142            int end = Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length());
143            return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : "");
144        }
145
146        private boolean areStringsEqual() {
147            return fExpected.equals(fActual);
148        }
149    }
150}