View Javadoc
1   /*
2    *  Copyright 2001-2013 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.beans.test;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.joda.beans.Bean;
23  import org.joda.beans.MetaProperty;
24  
25  /**
26   * Error class used when two beans fail to compare.
27   */
28  class BeanComparisonError extends AssertionError {
29  
30      /** Serialization version. */
31      private static final long serialVersionUID = 1L;
32  
33      /**
34       * The expected bean.
35       */
36      private final Bean expected;
37      /**
38       * The actual bean.
39       */
40      private final Bean actual;
41  
42      /**
43       * Creates a new error.
44       * 
45       * @param message  the message, may be null
46       * @param maxErrors  the maximum number of errors to report
47       * @param expected  the expected value, not null
48       * @param actual  the actual value, not null
49       */
50      public BeanComparisonError(final String message, final int maxErrors, final Bean expected, final Bean actual) {
51          super(buildMessage(message, maxErrors, expected, actual));
52          this.expected = expected;
53          this.actual = actual;
54      }
55  
56      //-----------------------------------------------------------------------
57      /**
58       * Compares the two beans.
59       * 
60       * @param message  the message, may be null
61       * @param maxErrors  the maximum number of errors to report
62       * @param expected  the expected value, not null
63       * @param actual  the actual value, not null
64       * @return the message, not null
65       */
66      private static String buildMessage(final String message, final int maxErrors, final Bean expected, final Bean actual) {
67          List<String> diffs = new ArrayList<String>();
68          buildMessage(diffs, "", expected, actual);
69          StringBuilder buf = new StringBuilder();
70          buf.append(message != null ? message + ": " : "");
71          buf.append("Bean did not equal expected. Differences:");
72          int size = diffs.size();
73          if (size > maxErrors) {
74              diffs = diffs.subList(0, maxErrors);
75          }
76          for (String diff : diffs) {
77              buf.append('\n').append(diff);
78          }
79          if (size > maxErrors) {
80              buf.append("\n...and " + (size - 10) + " more differences");
81          }
82          return buf.toString();
83      }
84  
85      private static void buildMessage(final List<String> diffs, final String prefix, final Object expected, final Object actual) {
86          if (expected == null && actual == null) {
87              return;
88          }
89          if (expected == null && actual != null) {
90              diffs.add(prefix + ": Expected null, but was " + buildSummary(actual, true));
91              return;
92          }
93          if (expected != null && actual == null) {
94              diffs.add(prefix + ": Was null, but expected " + buildSummary(expected, true));
95              return;
96          }
97          if (expected.getClass() != actual.getClass()) {
98              diffs.add(prefix + ": Class differs, expected " + buildSummary(expected, true) + " but was " + buildSummary(actual, true));
99              return;
100         }
101         if (expected instanceof List && actual instanceof List) {
102             List<?> expectedList = (List<?>) expected;
103             List<?> actualList = (List<?>) actual;
104             if (expectedList.size() != actualList.size()) {
105                 diffs.add(prefix + ": List size differs, expected " + expectedList.size() + " but was " + actualList.size());
106                 return;
107             }
108             for (int i = 0; i < expectedList.size(); i++) {
109                 buildMessage(diffs, prefix + '[' + i + "]", expectedList.get(i), actualList.get(i));
110             }
111             return;
112         }
113         if (expected instanceof Map && actual instanceof Map) {
114             Map<?, ?> expectedMap = (Map<?, ?>) expected;
115             Map<?, ?> actualMap = (Map<?, ?>) actual;
116             if (expectedMap.size() != actualMap.size()) {
117                 diffs.add(prefix + ": Map size differs, expected " + expectedMap.size() + " but was " + actualMap.size());
118                 return;
119             }
120             if (expectedMap.keySet().equals(actualMap.keySet()) == false) {
121                 diffs.add(prefix + ": Map keyset differs, expected " + buildSummary(expectedMap.keySet(), false) + " but was " + buildSummary(actualMap.keySet(), false));
122                 return;
123             }
124             for (Object key : expectedMap.keySet()) {
125                 buildMessage(diffs, prefix + '[' + key + "]", expectedMap.get(key), actualMap.get(key));
126             }
127             return;
128         }
129         if (expected.getClass() != actual.getClass()) {
130             diffs.add(prefix + ": Class differs, expected " + buildSummary(expected, true) + " but was " + buildSummary(actual, true));
131             return;
132         }
133         if (expected instanceof Bean) {
134             for (MetaProperty<?> prop : ((Bean) expected).metaBean().metaPropertyIterable()) {
135                 buildMessage(diffs, prefix + '.' + prop.name(), prop.get((Bean) expected), prop.get((Bean) actual));
136             }
137             return;
138         }
139         if (expected.equals(actual) == false) {
140             diffs.add(prefix + ": Content differs, expected " + buildSummary(expected, true) + " but was " + buildSummary(actual, false));
141             return;
142         }
143         return;  // equal
144     }
145 
146     /**
147      * Builds a summary of an object.
148      * 
149      * @param obj  the object to summarise, not null
150      */
151     private static String buildSummary(final Object obj, final boolean includeType) {
152         String type = obj.getClass().getSimpleName();
153         String toStr = obj.toString();
154         if (toStr.length() > 60) {
155             toStr = toStr.substring(0, 57) + "...";
156         }
157         return (includeType ? type + " " : "") + "<" + toStr + ">";
158     }
159 
160     //-------------------------------------------------------------------------
161     /**
162      * Gets the expected field.
163      * @return the expected
164      */
165     public Bean getExpected() {
166         return expected;
167     }
168 
169     /**
170      * Gets the actual field.
171      * @return the actual
172      */
173     public Bean getActual() {
174         return actual;
175     }
176 
177 }