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