blob: 82105eb85de3576342b617b267af7158104a68c6 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 */
23
24import java.beans.IntrospectionException;
25import java.beans.Introspector;
26import java.beans.PropertyDescriptor;
27
28import java.lang.reflect.Array;
29import java.lang.reflect.Field;
30import java.lang.reflect.InvocationTargetException;
31import java.lang.reflect.Method;
32import java.lang.reflect.Modifier;
33
34import java.util.ArrayList;
35import java.util.Collection;
36import java.util.IdentityHashMap;
37import java.util.Iterator;
38import java.util.List;
39import java.util.Map;
40import java.util.Queue;
41import java.util.SortedMap;
42import java.util.SortedSet;
43
44final class BeanValidator {
45 private final Map<Object, Object> cache = new IdentityHashMap<Object, Object>();
46
47 public void validate(Object object1, Object object2) {
48 // compare references
49 if (object1 == object2) {
50 return;
51 }
52 // check for null
53 if ((object1 == null) || (object2 == null)) {
54 throw new IllegalStateException("could not compare object with null");
55 }
56 // resolve self references
57 if (isCyclic(object1, object2)) {
58 return;
59 }
60 // resolve cross references
61 if (isCyclic(object2, object1)) {
62 return;
63 }
64 Class type = object1.getClass();
65 if (!type.equals(object2.getClass())) {
66 throw new IllegalStateException("could not compare objects with different types");
67 }
68 // validate elements of arrays
69 if (type.isArray()) {
70 int length = Array.getLength(object1);
71 if (length != Array.getLength(object2)) {
72 throw new IllegalStateException("could not compare arrays with different lengths");
73 }
74 try {
75 this.cache.put(object1, object2);
76 for (int i = 0; i < length; i++) {
77 log("validate array element", Integer.valueOf(i));
78 validate(Array.get(object1, i), Array.get(object2, i));
79 }
80 } finally {
81 this.cache.remove(object1);
82 }
83 return;
84 }
85 // validate objects using equals()
86 // we assume that the method equals(Object) can be called,
87 // if the class declares such method
88 if (isDefined(type, "equals", Object.class)) {
89 if (object1.equals(object2)) {
90 return;
91 }
92 throw new IllegalStateException("the first object is not equal to the second one");
93 }
94 // validate comparable objects using compareTo()
95 // we assume that the method compareTo(Object) can be called,
96 // if the class declares such method and implements interface Comparable
97 if (Comparable.class.isAssignableFrom(type) && isDefined(type, "compareTo", Object.class)) {
98 Comparable cmp = (Comparable) object1;
99 if (0 == cmp.compareTo(object2)) {
100 return;
101 }
102 throw new IllegalStateException("the first comparable object is not equal to the second one");
103 }
104 try {
105 this.cache.put(object1, object2);
106 // validate values of public fields
107 for (Field field : getFields(type)) {
108 int mod = field.getModifiers();
109 if (!Modifier.isStatic(mod)) {
110 log("validate field", field.getName());
111 validate(object1, object2, field);
112 }
113 }
114 // validate values of properties
115 for (PropertyDescriptor pd : getDescriptors(type)) {
116 Method method = pd.getReadMethod();
117 if (method != null) {
118 log("validate property", pd.getName());
119 validate(object1, object2, method);
120 }
121 }
122 // validate contents of maps
123 if (SortedMap.class.isAssignableFrom(type)) {
124 validate((Map) object1, (Map) object2, true);
125 } else if (Map.class.isAssignableFrom(type)) {
126 validate((Map) object1, (Map) object2, false);
127 }
128 // validate contents of collections
129 if (SortedSet.class.isAssignableFrom(type)) {
130 validate((Collection) object1, (Collection) object2, true);
131 } else if (List.class.isAssignableFrom(type)) {
132 validate((Collection) object1, (Collection) object2, true);
133 } else if (Queue.class.isAssignableFrom(type)) {
134 validate((Collection) object1, (Collection) object2, true);
135 } else if (Collection.class.isAssignableFrom(type)) {
136 validate((Collection) object1, (Collection) object2, false);
137 }
138 } finally {
139 this.cache.remove(object1);
140 }
141 }
142
143 private void validate(Object object1, Object object2, Field field) {
144 try {
145 object1 = field.get(object1);
146 object2 = field.get(object2);
147
148 validate(object1, object2);
149 }
150 catch (IllegalAccessException exception) {
151 log(exception);
152 }
153 }
154
155 private void validate(Object object1, Object object2, Method method) {
156 try {
157 object1 = method.invoke(object1);
158 object2 = method.invoke(object2);
159
160 validate(object1, object2);
161 }
162 catch (IllegalAccessException exception) {
163 log(exception);
164 }
165 catch (InvocationTargetException exception) {
166 log(exception.getCause());
167 }
168 }
169
170 private void validate(Collection c1, Collection c2, boolean sorted) {
171 if (c1.size() != c2.size()) {
172 throw new IllegalStateException("could not compare collections with different sizes");
173 }
174 if (sorted) {
175 Iterator first = c1.iterator();
176 Iterator second = c2.iterator();
177 for (int i = 0; first.hasNext() && second.hasNext(); i++) {
178 log("validate collection element", Integer.valueOf(i));
179 validate(first.next(), second.next());
180 }
181 if (first.hasNext() || second.hasNext()) {
182 throw new IllegalStateException("one collection contains more elements than another one");
183 }
184 } else {
185 List list = new ArrayList(c2);
186 Iterator first = c1.iterator();
187 for (int i = 0; first.hasNext(); i++) {
188 Object value = first.next();
189 log("validate collection element", Integer.valueOf(i));
190 Iterator second = list.iterator();
191 for (int j = 0; second.hasNext(); j++) {
192 log("validate collection element against", Integer.valueOf(j));
193 try {
194 validate(value, second.next());
195 second.remove();
196 break;
197 } catch (IllegalStateException exception) {
198 if (!second.hasNext()) {
199 throw new IllegalStateException("one collection does not contain some elements from another one", exception);
200 }
201 }
202 }
203 }
204 }
205 }
206
207 private void validate(Map map1, Map map2, boolean sorted) {
208 if (map1.size() != map2.size()) {
209 throw new IllegalStateException("could not compare maps with different sizes");
210 }
211 if (sorted) {
212 Iterator first = map1.entrySet().iterator();
213 Iterator second = map2.entrySet().iterator();
214 int index = 0;
215 while (first.hasNext() && second.hasNext()) {
216 log("validate map entry", Integer.valueOf(index++));
217 validate(first.next(), second.next());
218 }
219 if (first.hasNext() || second.hasNext()) {
220 throw new IllegalStateException("one map contains more entries than another one");
221 }
222 } else {
223 // assume that equals() can be used for keys
224 for (Object key : map1.keySet()) {
225 log("validate map value for key", key);
226 validate(map1.get(key), map2.get(key));
227 }
228 }
229 }
230
231 private boolean isCyclic(Object object1, Object object2) {
232 Object object = this.cache.get(object1);
233 if (object == null) {
234 return false;
235 }
236 if (object == object2) {
237 return true;
238 }
239 throw new IllegalStateException("could not resolve cyclic reference");
240 }
241
242 private boolean isDefined(Class type, String name, Class... params) {
243 try {
244 return type.equals(type.getMethod(name, params).getDeclaringClass());
245 }
246 catch (NoSuchMethodException exception) {
247 log(exception);
248 }
249 catch (SecurityException exception) {
250 log(exception);
251 }
252 return false;
253 }
254
255 private static final Field[] FIELDS = {};
256
257 private Field[] getFields(Class type) {
258 try {
259 return type.getFields();
260 }
261 catch (SecurityException exception) {
262 log(exception);
263 }
264 return FIELDS;
265 }
266
267 private static final PropertyDescriptor[] DESCRIPTORS = {};
268
269 private PropertyDescriptor[] getDescriptors(Class type) {
270 try {
271 return Introspector.getBeanInfo(type, Object.class).getPropertyDescriptors();
272 }
273 catch (IntrospectionException exception) {
274 log(exception);
275 }
276 return DESCRIPTORS;
277 }
278
279 private final StringBuilder sb = new StringBuilder(1024);
280
281 private void log(String message, Object value) {
282 this.sb.setLength(0);
283 int size = this.cache.size();
284 while (0 < size--) {
285 this.sb.append(" ");
286 }
287 this.sb.append(" - ");
288 this.sb.append(message);
289 if (value != null) {
290 this.sb.append(": ");
291 this.sb.append(value);
292 }
293 System.out.println(this.sb.toString());
294 }
295
296 private void log(Throwable throwable) {
297 this.sb.setLength(0);
298 int size = this.cache.size();
299 while (0 < size--) {
300 this.sb.append(" ");
301 }
302 this.sb.append(" ? ");
303 this.sb.append(throwable);
304 System.out.println(this.sb.toString());
305 }
306}