001    package 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     */
012    public 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        private String expected;
022        private String actual;
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            this.expected = expected;
034            this.actual = actual;
035        }
036    
037        /**
038         * Returns "..." in place of common prefix and "..." in place of common suffix between expected and actual.
039         *
040         * @see Throwable#getMessage()
041         */
042        @Override
043        public String getMessage() {
044            return new ComparisonCompactor(MAX_CONTEXT_LENGTH, expected, actual).compact(super.getMessage());
045        }
046    
047        /**
048         * Returns the actual string value
049         *
050         * @return the actual string value
051         */
052        public String getActual() {
053            return actual;
054        }
055    
056        /**
057         * Returns the expected string value
058         *
059         * @return the expected string value
060         */
061        public String getExpected() {
062            return expected;
063        }
064    
065        private static class ComparisonCompactor {
066            private static final String ELLIPSIS = "...";
067            private static final String DIFF_END = "]";
068            private static final String DIFF_START = "[";
069    
070            /**
071             * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When
072             * <code>contextLength</code> is exceeded, the Strings are shortened.
073             */
074            private final int contextLength;
075            private final String expected;
076            private final String actual;
077    
078            /**
079             * @param contextLength the maximum length of context surrounding the difference between the compared strings.
080             * When context length is exceeded, the prefixes and suffixes are compacted.
081             * @param expected the expected string value
082             * @param actual the actual string value
083             */
084            public ComparisonCompactor(int contextLength, String expected, String actual) {
085                this.contextLength = contextLength;
086                this.expected = expected;
087                this.actual = actual;
088            }
089    
090            public String compact(String message) {
091                if (expected == null || actual == null || expected.equals(actual)) {
092                    return Assert.format(message, expected, actual);
093                } else {
094                    DiffExtractor extractor = new DiffExtractor();
095                    String compactedPrefix = extractor.compactPrefix();
096                    String compactedSuffix = extractor.compactSuffix();
097                    return Assert.format(message,
098                            compactedPrefix + extractor.expectedDiff() + compactedSuffix,
099                            compactedPrefix + extractor.actualDiff() + compactedSuffix);
100                }
101            }
102    
103            private String sharedPrefix() {
104                int end = Math.min(expected.length(), actual.length());
105                for (int i = 0; i < end; i++) {
106                    if (expected.charAt(i) != actual.charAt(i)) {
107                        return expected.substring(0, i);
108                    }
109                }
110                return expected.substring(0, end);
111            }
112    
113            private String sharedSuffix(String prefix) {
114                int suffixLength = 0;
115                int maxSuffixLength = Math.min(expected.length() - prefix.length(),
116                        actual.length() - prefix.length()) - 1;
117                for (; suffixLength <= maxSuffixLength; suffixLength++) {
118                    if (expected.charAt(expected.length() - 1 - suffixLength)
119                            != actual.charAt(actual.length() - 1 - suffixLength)) {
120                        break;
121                    }
122                }
123                return expected.substring(expected.length() - suffixLength);
124            }
125    
126            private class DiffExtractor {
127                private final String sharedPrefix;
128                private final String sharedSuffix;
129    
130                /**
131                 * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}.
132                 */
133                private DiffExtractor() {
134                    sharedPrefix = sharedPrefix();
135                    sharedSuffix = sharedSuffix(sharedPrefix);
136                }
137    
138                public String expectedDiff() {
139                    return extractDiff(expected);
140                }
141    
142                public String actualDiff() {
143                    return extractDiff(actual);
144                }
145    
146                public String compactPrefix() {
147                    if (sharedPrefix.length() <= contextLength) {
148                        return sharedPrefix;
149                    }
150                    return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength);
151                }
152    
153                public String compactSuffix() {
154                    if (sharedSuffix.length() <= contextLength) {
155                        return sharedSuffix;
156                    }
157                    return sharedSuffix.substring(0, contextLength) + ELLIPSIS;
158                }
159    
160                private String extractDiff(String source) {
161                    return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length())
162                            + DIFF_END;
163                }
164            }
165        }
166    }