48 import java.lang.reflect.Modifier; |
48 import java.lang.reflect.Modifier; |
49 import java.math.BigInteger ; |
49 import java.math.BigInteger ; |
50 import java.math.BigDecimal ; |
50 import java.math.BigDecimal ; |
51 |
51 |
52 public final class ObjectUtility { |
52 public final class ObjectUtility { |
53 private boolean useToString ; |
53 private ObjectUtility() {} |
54 private boolean isIndenting ; |
|
55 private int initialLevel ; |
|
56 private int increment ; |
|
57 private ClassMap classToPrinter = new ClassMap() ; |
|
58 |
54 |
59 private static ObjectUtility standard = new ObjectUtility( false, true, |
|
60 0, 4 ) ; |
|
61 private static ObjectUtility compact = new ObjectUtility( true, false, |
|
62 0, 4 ) ; |
|
63 |
|
64 private ObjectUtility( boolean useToString, boolean isIndenting, |
|
65 int initialLevel, int increment ) |
|
66 { |
|
67 this.useToString = useToString ; |
|
68 this.isIndenting = isIndenting ; |
|
69 this.initialLevel = initialLevel ; |
|
70 this.increment = increment ; |
|
71 classToPrinter.put( Properties.class, propertiesPrinter ) ; |
|
72 classToPrinter.put( Collection.class, collectionPrinter ) ; |
|
73 classToPrinter.put( Map.class, mapPrinter ) ; |
|
74 } |
|
75 |
|
76 /** Construct an Utility instance with the desired objectToString |
|
77 * behavior. |
|
78 */ |
|
79 public static ObjectUtility make( boolean useToString, boolean isIndenting, |
|
80 int initialLevel, int increment ) |
|
81 { |
|
82 return new ObjectUtility( useToString, isIndenting, initialLevel, |
|
83 increment ) ; |
|
84 } |
|
85 |
|
86 /** Construct an Utility instance with the desired objectToString |
|
87 * behavior. |
|
88 */ |
|
89 public static ObjectUtility make( boolean useToString, boolean isIndenting ) |
|
90 { |
|
91 return new ObjectUtility( useToString, isIndenting, 0, 4 ) ; |
|
92 } |
|
93 |
|
94 /** Get the standard Utility object that supports objectToString with |
|
95 * indented display and no use of toString() methods. |
|
96 */ |
|
97 public static ObjectUtility make() |
|
98 { |
|
99 return standard ; |
|
100 } |
|
101 |
|
102 /** A convenience method that gives the default behavior: use indenting |
|
103 * to display the object's structure and do not use built-in toString |
|
104 * methods. |
|
105 */ |
|
106 public static String defaultObjectToString( java.lang.Object object ) |
|
107 { |
|
108 return standard.objectToString( object ) ; |
|
109 } |
|
110 |
|
111 public static String compactObjectToString( java.lang.Object object ) |
|
112 { |
|
113 return compact.objectToString( object ) ; |
|
114 } |
|
115 |
|
116 /** objectToString handles display of arbitrary objects. It correctly |
|
117 * handles objects whose elements form an arbitrary graph. It uses |
|
118 * reflection to display the contents of any kind of object. |
|
119 * An object's toString() method may optionally be used, but the default |
|
120 * is to ignore all toString() methods except for those defined for |
|
121 * primitive types, primitive type wrappers, and strings. |
|
122 */ |
|
123 public String objectToString(java.lang.Object obj) |
|
124 { |
|
125 IdentityHashMap printed = new IdentityHashMap() ; |
|
126 ObjectWriter result = ObjectWriter.make( isIndenting, initialLevel, |
|
127 increment ) ; |
|
128 objectToStringHelper( printed, result, obj ) ; |
|
129 return result.toString() ; |
|
130 } |
|
131 |
|
132 // Perform a deep structural equality comparison of the two objects. |
|
133 // This handles all arrays, maps, and sets specially, otherwise |
|
134 // it just calls the object's equals() method. |
|
135 public static boolean equals( java.lang.Object obj1, java.lang.Object obj2 ) |
|
136 { |
|
137 // Set of pairs of objects that have been (or are being) considered for |
|
138 // equality. Such pairs are presumed to be equals. If they are not, |
|
139 // this will be detected eventually and the equals method will return |
|
140 // false. |
|
141 Set considered = new HashSet() ; |
|
142 |
|
143 // Map that gives the corresponding component of obj2 for a component |
|
144 // of obj1. This is used to check for the same aliasing and use of |
|
145 // equal objects in both objects. |
|
146 Map counterpart = new IdentityHashMap() ; |
|
147 |
|
148 return equalsHelper( counterpart, considered, obj1, obj2 ) ; |
|
149 } |
|
150 |
55 |
151 /** If arr1 and arr2 are both arrays of the same component type, |
56 /** If arr1 and arr2 are both arrays of the same component type, |
152 * return an array of that component type that consists of the |
57 * return an array of that component type that consists of the |
153 * elements of arr1 followed by the elements of arr2. |
58 * elements of arr1 followed by the elements of arr2. |
154 * Throws IllegalArgumentException otherwise. |
59 * Throws IllegalArgumentException otherwise. |
177 Array.set( result, index++, Array.get( arr2, ctr ) ) ; |
82 Array.set( result, index++, Array.get( arr2, ctr ) ) ; |
178 |
83 |
179 return result ; |
84 return result ; |
180 } |
85 } |
181 |
86 |
182 //=========================================================================== |
|
183 // Implementation |
|
184 //=========================================================================== |
|
185 |
|
186 private void objectToStringHelper( IdentityHashMap printed, |
|
187 ObjectWriter result, java.lang.Object obj) |
|
188 { |
|
189 if (obj==null) { |
|
190 result.append( "null" ) ; |
|
191 result.endElement() ; |
|
192 } else { |
|
193 Class cls = obj.getClass() ; |
|
194 result.startObject( obj ) ; |
|
195 |
|
196 if (printed.keySet().contains( obj )) { |
|
197 result.endObject( "*VISITED*" ) ; |
|
198 } else { |
|
199 printed.put( obj, null ) ; |
|
200 |
|
201 if (mustUseToString(cls)) { |
|
202 result.endObject( obj.toString() ) ; |
|
203 } else { |
|
204 // First, handle any classes that have special printer |
|
205 // methods defined. This is useful when the class |
|
206 // overrides toString with something that |
|
207 // is not sufficiently detailed. |
|
208 ObjectPrinter printer = (ObjectPrinter)(classToPrinter.get( |
|
209 cls )) ; |
|
210 if (printer != null) { |
|
211 printer.print( printed, result, obj ) ; |
|
212 result.endObject() ; |
|
213 } else { |
|
214 Class compClass = cls.getComponentType() ; |
|
215 |
|
216 if (compClass == null) |
|
217 // handleObject always calls endObject |
|
218 handleObject( printed, result, obj ) ; |
|
219 else { |
|
220 handleArray( printed, result, obj ) ; |
|
221 result.endObject() ; |
|
222 } |
|
223 } |
|
224 } |
|
225 } |
|
226 } |
|
227 } |
|
228 |
|
229 private static interface ObjectPrinter { |
|
230 void print( IdentityHashMap printed, ObjectWriter buff, |
|
231 java.lang.Object obj ) ; |
|
232 } |
|
233 |
|
234 private ObjectPrinter propertiesPrinter = new ObjectPrinter() { |
|
235 public void print( IdentityHashMap printed, ObjectWriter buff, |
|
236 java.lang.Object obj ) |
|
237 { |
|
238 if (!(obj instanceof Properties)) |
|
239 throw new Error() ; |
|
240 |
|
241 Properties props = (Properties)obj ; |
|
242 Enumeration keys = props.propertyNames() ; |
|
243 while (keys.hasMoreElements()) { |
|
244 String key = (String)(keys.nextElement()) ; |
|
245 String value = props.getProperty( key ) ; |
|
246 buff.startElement() ; |
|
247 buff.append( key ) ; |
|
248 buff.append( "=" ) ; |
|
249 buff.append( value ) ; |
|
250 buff.endElement() ; |
|
251 } |
|
252 } |
|
253 } ; |
|
254 |
|
255 private ObjectPrinter collectionPrinter = new ObjectPrinter() { |
|
256 public void print( IdentityHashMap printed, ObjectWriter buff, |
|
257 java.lang.Object obj ) |
|
258 { |
|
259 if (!(obj instanceof Collection)) |
|
260 throw new Error() ; |
|
261 |
|
262 Collection coll = (Collection)obj ; |
|
263 Iterator iter = coll.iterator() ; |
|
264 while (iter.hasNext()) { |
|
265 java.lang.Object element = iter.next() ; |
|
266 buff.startElement() ; |
|
267 objectToStringHelper( printed, buff, element ) ; |
|
268 buff.endElement() ; |
|
269 } |
|
270 } |
|
271 } ; |
|
272 |
|
273 private ObjectPrinter mapPrinter = new ObjectPrinter() { |
|
274 public void print( IdentityHashMap printed, ObjectWriter buff, |
|
275 java.lang.Object obj ) |
|
276 { |
|
277 if (!(obj instanceof Map)) |
|
278 throw new Error() ; |
|
279 |
|
280 Map map = (Map)obj ; |
|
281 Iterator iter = map.entrySet().iterator() ; |
|
282 while (iter.hasNext()) { |
|
283 Entry entry = (Entry)(iter.next()) ; |
|
284 buff.startElement() ; |
|
285 objectToStringHelper( printed, buff, entry.getKey() ) ; |
|
286 buff.append( "=>" ) ; |
|
287 objectToStringHelper( printed, buff, entry.getValue() ) ; |
|
288 buff.endElement() ; |
|
289 } |
|
290 } |
|
291 } ; |
|
292 |
|
293 private static class ClassMap { |
|
294 ArrayList data ; |
|
295 |
|
296 public ClassMap() |
|
297 { |
|
298 data = new ArrayList() ; |
|
299 } |
|
300 |
|
301 /** Return the first element of the ClassMap that is assignable to cls. |
|
302 * The order is determined by the order in which the put method was |
|
303 * called. Returns null if there is no match. |
|
304 */ |
|
305 public java.lang.Object get( Class cls ) |
|
306 { |
|
307 Iterator iter = data.iterator() ; |
|
308 while (iter.hasNext()) { |
|
309 java.lang.Object[] arr = (java.lang.Object[])(iter.next()) ; |
|
310 Class key = (Class)(arr[0]) ; |
|
311 if (key.isAssignableFrom( cls )) |
|
312 return arr[1] ; |
|
313 } |
|
314 |
|
315 return null ; |
|
316 } |
|
317 |
|
318 /** Add obj to the map with key cls. Note that order matters, |
|
319 * as the first match is returned. |
|
320 */ |
|
321 public void put( Class cls, java.lang.Object obj ) |
|
322 { |
|
323 java.lang.Object[] pair = { cls, obj } ; |
|
324 data.add( pair ) ; |
|
325 } |
|
326 } |
|
327 |
|
328 private boolean mustUseToString( Class cls ) |
|
329 { |
|
330 // These probably never occur |
|
331 if (cls.isPrimitive()) |
|
332 return true ; |
|
333 |
|
334 // We must use toString for all primitive wrappers, since |
|
335 // otherwise the code recurses endlessly (access value field |
|
336 // inside Integer, returns another Integer through reflection). |
|
337 if ((cls == Integer.class) || |
|
338 (cls == BigInteger.class) || |
|
339 (cls == BigDecimal.class) || |
|
340 (cls == String.class) || |
|
341 (cls == StringBuffer.class) || |
|
342 (cls == Long.class) || |
|
343 (cls == Short.class) || |
|
344 (cls == Byte.class) || |
|
345 (cls == Character.class) || |
|
346 (cls == Float.class) || |
|
347 (cls == Double.class) || |
|
348 (cls == Boolean.class)) |
|
349 return true ; |
|
350 |
|
351 if (useToString) { |
|
352 try { |
|
353 cls.getDeclaredMethod( "toString", (Class[])null ) ; |
|
354 return true ; |
|
355 } catch (Exception exc) { |
|
356 return false ; |
|
357 } |
|
358 } |
|
359 |
|
360 return false ; |
|
361 } |
|
362 |
|
363 private void handleObject( IdentityHashMap printed, ObjectWriter result, |
|
364 java.lang.Object obj ) |
|
365 { |
|
366 Class cls = obj.getClass() ; |
|
367 |
|
368 try { |
|
369 Field[] fields; |
|
370 SecurityManager security = System.getSecurityManager(); |
|
371 if (security != null && !Modifier.isPublic(cls.getModifiers())) { |
|
372 fields = new Field[0]; |
|
373 } else { |
|
374 fields = cls.getDeclaredFields(); |
|
375 } |
|
376 |
|
377 for (int ctr=0; ctr<fields.length; ctr++ ) { |
|
378 final Field fld = fields[ctr] ; |
|
379 int modifiers = fld.getModifiers() ; |
|
380 |
|
381 // Do not display field if it is static, since these fields |
|
382 // are always the same for every instances. This could |
|
383 // be made configurable, but I don't think it is |
|
384 // useful to do so. |
|
385 if (!Modifier.isStatic( modifiers )) { |
|
386 if (security != null) { |
|
387 if (!Modifier.isPublic(modifiers)) |
|
388 continue; |
|
389 } |
|
390 result.startElement() ; |
|
391 result.append( fld.getName() ) ; |
|
392 result.append( ":" ) ; |
|
393 |
|
394 try { |
|
395 // Make sure that we can read the field if it is |
|
396 // not public |
|
397 AccessController.doPrivileged( new PrivilegedAction() { |
|
398 public Object run() { |
|
399 fld.setAccessible( true ) ; |
|
400 return null ; |
|
401 } |
|
402 } ) ; |
|
403 |
|
404 java.lang.Object value = fld.get( obj ) ; |
|
405 objectToStringHelper( printed, result, value ) ; |
|
406 } catch (Exception exc2) { |
|
407 result.append( "???" ) ; |
|
408 } |
|
409 |
|
410 result.endElement() ; |
|
411 } |
|
412 } |
|
413 |
|
414 result.endObject() ; |
|
415 } catch (Exception exc2) { |
|
416 result.endObject( obj.toString() ) ; |
|
417 } |
|
418 } |
|
419 |
|
420 private void handleArray( IdentityHashMap printed, ObjectWriter result, |
|
421 java.lang.Object obj ) |
|
422 { |
|
423 Class compClass = obj.getClass().getComponentType() ; |
|
424 if (compClass == boolean.class) { |
|
425 boolean[] arr = (boolean[])obj ; |
|
426 for (int ctr=0; ctr<arr.length; ctr++) { |
|
427 result.startElement() ; |
|
428 result.append( arr[ctr] ) ; |
|
429 result.endElement() ; |
|
430 } |
|
431 } else if (compClass == byte.class) { |
|
432 byte[] arr = (byte[])obj ; |
|
433 for (int ctr=0; ctr<arr.length; ctr++) { |
|
434 result.startElement() ; |
|
435 result.append( arr[ctr] ) ; |
|
436 result.endElement() ; |
|
437 } |
|
438 } else if (compClass == short.class) { |
|
439 short[] arr = (short[])obj ; |
|
440 for (int ctr=0; ctr<arr.length; ctr++) { |
|
441 result.startElement() ; |
|
442 result.append( arr[ctr] ) ; |
|
443 result.endElement() ; |
|
444 } |
|
445 } else if (compClass == int.class) { |
|
446 int[] arr = (int[])obj ; |
|
447 for (int ctr=0; ctr<arr.length; ctr++) { |
|
448 result.startElement() ; |
|
449 result.append( arr[ctr] ) ; |
|
450 result.endElement() ; |
|
451 } |
|
452 } else if (compClass == long.class) { |
|
453 long[] arr = (long[])obj ; |
|
454 for (int ctr=0; ctr<arr.length; ctr++) { |
|
455 result.startElement() ; |
|
456 result.append( arr[ctr] ) ; |
|
457 result.endElement() ; |
|
458 } |
|
459 } else if (compClass == char.class) { |
|
460 char[] arr = (char[])obj ; |
|
461 for (int ctr=0; ctr<arr.length; ctr++) { |
|
462 result.startElement() ; |
|
463 result.append( arr[ctr] ) ; |
|
464 result.endElement() ; |
|
465 } |
|
466 } else if (compClass == float.class) { |
|
467 float[] arr = (float[])obj ; |
|
468 for (int ctr=0; ctr<arr.length; ctr++) { |
|
469 result.startElement() ; |
|
470 result.append( arr[ctr] ) ; |
|
471 result.endElement() ; |
|
472 } |
|
473 } else if (compClass == double.class) { |
|
474 double[] arr = (double[])obj ; |
|
475 for (int ctr=0; ctr<arr.length; ctr++) { |
|
476 result.startElement() ; |
|
477 result.append( arr[ctr] ) ; |
|
478 result.endElement() ; |
|
479 } |
|
480 } else { // array of object |
|
481 java.lang.Object[] arr = (java.lang.Object[])obj ; |
|
482 for (int ctr=0; ctr<arr.length; ctr++) { |
|
483 result.startElement() ; |
|
484 objectToStringHelper( printed, result, arr[ctr] ) ; |
|
485 result.endElement() ; |
|
486 } |
|
487 } |
|
488 } |
|
489 |
|
490 private static class Pair |
|
491 { |
|
492 private java.lang.Object obj1 ; |
|
493 private java.lang.Object obj2 ; |
|
494 |
|
495 Pair( java.lang.Object obj1, java.lang.Object obj2 ) |
|
496 { |
|
497 this.obj1 = obj1 ; |
|
498 this.obj2 = obj2 ; |
|
499 } |
|
500 |
|
501 public boolean equals( java.lang.Object obj ) |
|
502 { |
|
503 if (!(obj instanceof Pair)) |
|
504 return false ; |
|
505 |
|
506 Pair other = (Pair)obj ; |
|
507 return other.obj1 == obj1 && other.obj2 == obj2 ; |
|
508 } |
|
509 |
|
510 public int hashCode() |
|
511 { |
|
512 return System.identityHashCode( obj1 ) ^ |
|
513 System.identityHashCode( obj2 ) ; |
|
514 } |
|
515 } |
|
516 |
|
517 private static boolean equalsHelper( Map counterpart, Set considered, |
|
518 java.lang.Object obj1, java.lang.Object obj2 ) |
|
519 { |
|
520 if ((obj1 == null) || (obj2 == null)) |
|
521 return obj1 == obj2 ; |
|
522 |
|
523 java.lang.Object other2 = counterpart.get( obj1 ) ; |
|
524 if (other2 == null) { |
|
525 other2 = obj2 ; |
|
526 counterpart.put( obj1, other2 ) ; |
|
527 } |
|
528 |
|
529 if (obj1 == other2) |
|
530 return true ; |
|
531 |
|
532 if (obj2 != other2) |
|
533 return false ; |
|
534 |
|
535 Pair pair = new Pair( obj1, obj2 ) ; |
|
536 if (considered.contains( pair )) |
|
537 return true ; |
|
538 else |
|
539 considered.add( pair ) ; |
|
540 |
|
541 if (obj1 instanceof java.lang.Object[] && |
|
542 obj2 instanceof java.lang.Object[]) |
|
543 return equalArrays( counterpart, considered, |
|
544 (java.lang.Object[])obj1, (java.lang.Object[])obj2 ) ; |
|
545 else if (obj1 instanceof Map && obj2 instanceof Map) |
|
546 return equalMaps( counterpart, considered, |
|
547 (Map)obj1, (Map)obj2 ) ; |
|
548 else if (obj1 instanceof Set && obj2 instanceof Set) |
|
549 return equalSets( counterpart, considered, |
|
550 (Set)obj1, (Set)obj2 ) ; |
|
551 else if (obj1 instanceof List && obj2 instanceof List) |
|
552 return equalLists( counterpart, considered, |
|
553 (List)obj1, (List)obj2 ) ; |
|
554 else if (obj1 instanceof boolean[] && obj2 instanceof boolean[]) |
|
555 return Arrays.equals( (boolean[])obj1, (boolean[])obj2 ) ; |
|
556 else if (obj1 instanceof byte[] && obj2 instanceof byte[]) |
|
557 return Arrays.equals( (byte[])obj1, (byte[])obj2 ) ; |
|
558 else if (obj1 instanceof char[] && obj2 instanceof char[]) |
|
559 return Arrays.equals( (char[])obj1, (char[])obj2 ) ; |
|
560 else if (obj1 instanceof double[] && obj2 instanceof double[]) |
|
561 return Arrays.equals( (double[])obj1, (double[])obj2 ) ; |
|
562 else if (obj1 instanceof float[] && obj2 instanceof float[]) |
|
563 return Arrays.equals( (float[])obj1, (float[])obj2 ) ; |
|
564 else if (obj1 instanceof int[] && obj2 instanceof int[]) |
|
565 return Arrays.equals( (int[])obj1, (int[])obj2 ) ; |
|
566 else if (obj1 instanceof long[] && obj2 instanceof long[]) |
|
567 return Arrays.equals( (long[])obj1, (long[])obj2 ) ; |
|
568 else { |
|
569 Class cls = obj1.getClass() ; |
|
570 if (cls != obj2.getClass()) |
|
571 return obj1.equals( obj2 ) ; |
|
572 else |
|
573 return equalsObject( counterpart, considered, cls, obj1, obj2 ) ; |
|
574 } |
|
575 } |
|
576 |
|
577 private static boolean equalsObject( Map counterpart, Set considered, |
|
578 Class cls, java.lang.Object obj1, java.lang.Object obj2 ) |
|
579 { |
|
580 Class objectClass = java.lang.Object.class ; |
|
581 if (cls == objectClass) |
|
582 return true ; |
|
583 |
|
584 Class[] equalsTypes = { objectClass } ; |
|
585 try { |
|
586 Method equalsMethod = cls.getDeclaredMethod( "equals", |
|
587 equalsTypes ) ; |
|
588 return obj1.equals( obj2 ) ; |
|
589 } catch (Exception exc) { |
|
590 if (equalsObjectFields( counterpart, considered, |
|
591 cls, obj1, obj2 )) |
|
592 return equalsObject( counterpart, considered, |
|
593 cls.getSuperclass(), obj1, obj2 ) ; |
|
594 else |
|
595 return false ; |
|
596 } |
|
597 } |
|
598 |
|
599 private static boolean equalsObjectFields( Map counterpart, Set considered, |
|
600 Class cls, java.lang.Object obj1, java.lang.Object obj2 ) |
|
601 { |
|
602 Field[] fields = cls.getDeclaredFields() ; |
|
603 for (int ctr=0; ctr<fields.length; ctr++) { |
|
604 try { |
|
605 final Field field = fields[ctr] ; |
|
606 // Ignore static fields |
|
607 if (!Modifier.isStatic( field.getModifiers())) { |
|
608 AccessController.doPrivileged(new PrivilegedAction() { |
|
609 public Object run() { |
|
610 field.setAccessible( true ) ; |
|
611 return null ; |
|
612 } |
|
613 } ) ; |
|
614 |
|
615 java.lang.Object value1 = field.get( obj1 ) ; |
|
616 java.lang.Object value2 = field.get( obj2 ) ; |
|
617 if (!equalsHelper( counterpart, considered, value1, |
|
618 value2 )) |
|
619 return false ; |
|
620 } |
|
621 } catch (IllegalAccessException exc) { |
|
622 return false ; |
|
623 } |
|
624 } |
|
625 |
|
626 return true ; |
|
627 } |
|
628 |
|
629 private static boolean equalArrays( Map counterpart, Set considered, |
|
630 java.lang.Object[] arr1, java.lang.Object[] arr2 ) |
|
631 { |
|
632 int len = arr1.length ; |
|
633 if (len != arr2.length) |
|
634 return false ; |
|
635 |
|
636 for (int ctr = 0; ctr<len; ctr++ ) |
|
637 if (!equalsHelper( counterpart, considered, arr1[ctr], arr2[ctr] )) |
|
638 return false ; |
|
639 |
|
640 return true ; |
|
641 } |
|
642 |
|
643 private static boolean equalMaps( Map counterpart, Set considered, |
|
644 Map map1, Map map2 ) |
|
645 { |
|
646 if (map2.size() != map1.size()) |
|
647 return false; |
|
648 |
|
649 try { |
|
650 Iterator i = map1.entrySet().iterator(); |
|
651 while (i.hasNext()) { |
|
652 Entry e = (Entry) i.next(); |
|
653 java.lang.Object key = e.getKey(); |
|
654 java.lang.Object value = e.getValue(); |
|
655 if (value == null) { |
|
656 if (!(map2.get(key)==null && map2.containsKey(key))) |
|
657 return false; |
|
658 } else { |
|
659 if (!equalsHelper( counterpart, considered, |
|
660 value, map2.get(key))) |
|
661 return false; |
|
662 } |
|
663 } |
|
664 } catch(ClassCastException unused) { |
|
665 return false; |
|
666 } catch(NullPointerException unused) { |
|
667 return false; |
|
668 } |
|
669 |
|
670 return true; |
|
671 } |
|
672 |
|
673 // Obviously this is an inefficient quadratic algorithm. |
|
674 // This is taken pretty directly from AbstractSet and AbstractCollection |
|
675 // in the JDK. |
|
676 // For HashSet, an O(n) (with a good hash function) algorithm |
|
677 // is possible, and likewise TreeSet, since it is |
|
678 // ordered, is O(n). But this is not worth the effort here. |
|
679 // Note that the inner loop uses equals, not equalsHelper. |
|
680 // This is needed because of the searching behavior of this test. |
|
681 // However, note that this will NOT correctly handle sets that |
|
682 // contain themselves as members, or that have members that reference |
|
683 // themselves. These cases will cause infinite regress! |
|
684 private static boolean equalSets( Map counterpart, Set considered, |
|
685 Set set1, Set set2 ) |
|
686 { |
|
687 if (set1.size() != set2.size()) |
|
688 return false ; |
|
689 |
|
690 Iterator e1 = set1.iterator() ; |
|
691 while (e1.hasNext()) { |
|
692 java.lang.Object obj1 = e1.next() ; |
|
693 |
|
694 boolean found = false ; |
|
695 Iterator e2 = set2.iterator() ; |
|
696 while (e2.hasNext() && !found) { |
|
697 java.lang.Object obj2 = e2.next() ; |
|
698 found = equals( obj1, obj2 ) ; |
|
699 } |
|
700 |
|
701 if (!found) |
|
702 return false ; |
|
703 } |
|
704 |
|
705 return true ; |
|
706 } |
|
707 |
|
708 private static boolean equalLists( Map counterpart, Set considered, |
|
709 List list1, List list2 ) |
|
710 { |
|
711 ListIterator e1 = list1.listIterator(); |
|
712 ListIterator e2 = list2.listIterator(); |
|
713 while(e1.hasNext() && e2.hasNext()) { |
|
714 java.lang.Object o1 = e1.next(); |
|
715 java.lang.Object o2 = e2.next(); |
|
716 if (!(o1==null ? o2==null : equalsHelper( |
|
717 counterpart, considered, o1, o2))) |
|
718 return false; |
|
719 } |
|
720 return !(e1.hasNext() || e2.hasNext()); |
|
721 } |
|
722 } |
87 } |