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        /*
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    }