001package org.junit;
002
003/**
004 * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails.
005 * Create and throw a <code>ComparisonFailure</code> manually if you want to show users the
006 * difference between two complex strings.
007 * <p/>
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 expected and actual strings. 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    /*
022     * We have to use the f prefix until the next major release to ensure
023     * serialization compatibility. 
024     * See https://github.com/junit-team/junit4/issues/976
025     */
026    private String fExpected;
027    private String fActual;
028
029    /**
030     * Constructs a comparison failure.
031     *
032     * @param message the identifying message or null
033     * @param expected the expected string value
034     * @param actual the actual string value
035     */
036    public ComparisonFailure(String message, String expected, String actual) {
037        super(message);
038        this.fExpected = expected;
039        this.fActual = actual;
040    }
041
042    /**
043     * Returns "..." in place of common prefix and "..." in place of common suffix between expected and actual.
044     *
045     * @see Throwable#getMessage()
046     */
047    @Override
048    public String getMessage() {
049        return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
050    }
051
052    /**
053     * Returns the actual string value
054     *
055     * @return the actual string value
056     */
057    public String getActual() {
058        return fActual;
059    }
060
061    /**
062     * Returns the expected string value
063     *
064     * @return the expected string value
065     */
066    public String getExpected() {
067        return fExpected;
068    }
069
070    private static class ComparisonCompactor {
071        private static final String ELLIPSIS = "...";
072        private static final String DIFF_END = "]";
073        private static final String DIFF_START = "[";
074
075        /**
076         * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When
077         * <code>contextLength</code> is exceeded, the Strings are shortened.
078         */
079        private final int contextLength;
080        private final String expected;
081        private final String actual;
082
083        /**
084         * @param contextLength the maximum length of context surrounding the difference between the compared strings.
085         * When context length is exceeded, the prefixes and suffixes are compacted.
086         * @param expected the expected string value
087         * @param actual the actual string value
088         */
089        public ComparisonCompactor(int contextLength, String expected, String actual) {
090            this.contextLength = contextLength;
091            this.expected = expected;
092            this.actual = actual;
093        }
094
095        public String compact(String message) {
096            if (expected == null || actual == null || expected.equals(actual)) {
097                return Assert.format(message, expected, actual);
098            } else {
099                DiffExtractor extractor = new DiffExtractor();
100                String compactedPrefix = extractor.compactPrefix();
101                String compactedSuffix = extractor.compactSuffix();
102                return Assert.format(message,
103                        compactedPrefix + extractor.expectedDiff() + compactedSuffix,
104                        compactedPrefix + extractor.actualDiff() + compactedSuffix);
105            }
106        }
107
108        private String sharedPrefix() {
109            int end = Math.min(expected.length(), actual.length());
110            for (int i = 0; i < end; i++) {
111                if (expected.charAt(i) != actual.charAt(i)) {
112                    return expected.substring(0, i);
113                }
114            }
115            return expected.substring(0, end);
116        }
117
118        private String sharedSuffix(String prefix) {
119            int suffixLength = 0;
120            int maxSuffixLength = Math.min(expected.length() - prefix.length(),
121                    actual.length() - prefix.length()) - 1;
122            for (; suffixLength <= maxSuffixLength; suffixLength++) {
123                if (expected.charAt(expected.length() - 1 - suffixLength)
124                        != actual.charAt(actual.length() - 1 - suffixLength)) {
125                    break;
126                }
127            }
128            return expected.substring(expected.length() - suffixLength);
129        }
130
131        private class DiffExtractor {
132            private final String sharedPrefix;
133            private final String sharedSuffix;
134
135            /**
136             * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}.
137             */
138            private DiffExtractor() {
139                sharedPrefix = sharedPrefix();
140                sharedSuffix = sharedSuffix(sharedPrefix);
141            }
142
143            public String expectedDiff() {
144                return extractDiff(expected);
145            }
146
147            public String actualDiff() {
148                return extractDiff(actual);
149            }
150
151            public String compactPrefix() {
152                if (sharedPrefix.length() <= contextLength) {
153                    return sharedPrefix;
154                }
155                return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength);
156            }
157
158            public String compactSuffix() {
159                if (sharedSuffix.length() <= contextLength) {
160                    return sharedSuffix;
161                }
162                return sharedSuffix.substring(0, contextLength) + ELLIPSIS;
163            }
164
165            private String extractDiff(String source) {
166                return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length())
167                        + DIFF_END;
168            }
169        }
170    }
171}