1package org.junit;
2
3/**
4 * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. Create and throw
5 * a <code>ComparisonFailure</code> manually if you want to show users the difference between two complex
6 * strings.
7 *
8 * Inspired by a patch from Alex Chaffee (alex@purpletech.com)
9 */
10public class ComparisonFailure extends AssertionError {
11	/**
12	 * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened.
13	 * @see ComparisonCompactor
14	 */
15	private static final int MAX_CONTEXT_LENGTH= 20;
16	private static final long serialVersionUID= 1L;
17
18	private String fExpected;
19	private String fActual;
20
21	/**
22	 * Constructs a comparison failure.
23	 * @param message the identifying message or null
24	 * @param expected the expected string value
25	 * @param actual the actual string value
26	 */
27	public ComparisonFailure (String message, String expected, String actual) {
28		super (message);
29		fExpected= expected;
30		fActual= actual;
31	}
32
33	/**
34	 * Returns "..." in place of common prefix and "..." in
35	 * place of common suffix between expected and actual.
36	 *
37	 * @see Throwable#getMessage()
38	 */
39	@Override
40	public String getMessage() {
41		return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
42	}
43
44	/**
45	 * Returns the actual string value
46	 * @return the actual string value
47	 */
48	public String getActual() {
49		return fActual;
50	}
51	/**
52	 * Returns the expected string value
53	 * @return the expected string value
54	 */
55	public String getExpected() {
56		return fExpected;
57	}
58
59	private static class ComparisonCompactor {
60		private static final String ELLIPSIS= "...";
61		private static final String DELTA_END= "]";
62		private static final String DELTA_START= "[";
63
64		/**
65		 * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code>
66		 * is exceeded, the Strings are shortened
67		 */
68		private int fContextLength;
69		private String fExpected;
70		private String fActual;
71		private int fPrefix;
72		private int fSuffix;
73
74		/**
75		 * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength
76		 * is exceeded, the Strings are shortened
77		 * @param expected the expected string value
78		 * @param actual the actual string value
79		 */
80		public ComparisonCompactor(int contextLength, String expected, String actual) {
81			fContextLength= contextLength;
82			fExpected= expected;
83			fActual= actual;
84		}
85
86		private String compact(String message) {
87			if (fExpected == null || fActual == null || areStringsEqual())
88				return Assert.format(message, fExpected, fActual);
89
90			findCommonPrefix();
91			findCommonSuffix();
92			String expected= compactString(fExpected);
93			String actual= compactString(fActual);
94			return Assert.format(message, expected, actual);
95		}
96
97		private String compactString(String source) {
98			String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
99			if (fPrefix > 0)
100				result= computeCommonPrefix() + result;
101			if (fSuffix > 0)
102				result= result + computeCommonSuffix();
103			return result;
104		}
105
106		private void findCommonPrefix() {
107			fPrefix= 0;
108			int end= Math.min(fExpected.length(), fActual.length());
109			for (; fPrefix < end; fPrefix++) {
110				if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix))
111					break;
112			}
113		}
114
115		private void findCommonSuffix() {
116			int expectedSuffix= fExpected.length() - 1;
117			int actualSuffix= fActual.length() - 1;
118			for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) {
119				if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix))
120					break;
121			}
122			fSuffix=  fExpected.length() - expectedSuffix;
123		}
124
125		private String computeCommonPrefix() {
126			return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
127		}
128
129		private String computeCommonSuffix() {
130			int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length());
131			return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : "");
132		}
133
134		private boolean areStringsEqual() {
135			return fExpected.equals(fActual);
136		}
137	}
138}