/* * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.corba.se.impl.orbutil; import java.security.PrivilegedAction; import java.security.AccessController; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.Map.Entry; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Enumeration; import java.util.Properties; import java.util.IdentityHashMap; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger ; import java.math.BigDecimal ; public final class ObjectUtility { private boolean useToString ; private boolean isIndenting ; private int initialLevel ; private int increment ; private ClassMap classToPrinter = new ClassMap() ; private static ObjectUtility standard = new ObjectUtility( false, true, 0, 4 ) ; private static ObjectUtility compact = new ObjectUtility( true, false, 0, 4 ) ; private ObjectUtility( boolean useToString, boolean isIndenting, int initialLevel, int increment ) { this.useToString = useToString ; this.isIndenting = isIndenting ; this.initialLevel = initialLevel ; this.increment = increment ; classToPrinter.put( Properties.class, propertiesPrinter ) ; classToPrinter.put( Collection.class, collectionPrinter ) ; classToPrinter.put( Map.class, mapPrinter ) ; } /** Construct an Utility instance with the desired objectToString * behavior. */ public static ObjectUtility make( boolean useToString, boolean isIndenting, int initialLevel, int increment ) { return new ObjectUtility( useToString, isIndenting, initialLevel, increment ) ; } /** Construct an Utility instance with the desired objectToString * behavior. */ public static ObjectUtility make( boolean useToString, boolean isIndenting ) { return new ObjectUtility( useToString, isIndenting, 0, 4 ) ; } /** Get the standard Utility object that supports objectToString with * indented display and no use of toString() methods. */ public static ObjectUtility make() { return standard ; } /** A convenience method that gives the default behavior: use indenting * to display the object's structure and do not use built-in toString * methods. */ public static String defaultObjectToString( java.lang.Object object ) { return standard.objectToString( object ) ; } public static String compactObjectToString( java.lang.Object object ) { return compact.objectToString( object ) ; } /** objectToString handles display of arbitrary objects. It correctly * handles objects whose elements form an arbitrary graph. It uses * reflection to display the contents of any kind of object. * An object's toString() method may optionally be used, but the default * is to ignore all toString() methods except for those defined for * primitive types, primitive type wrappers, and strings. */ public String objectToString(java.lang.Object obj) { IdentityHashMap printed = new IdentityHashMap() ; ObjectWriter result = ObjectWriter.make( isIndenting, initialLevel, increment ) ; objectToStringHelper( printed, result, obj ) ; return result.toString() ; } // Perform a deep structural equality comparison of the two objects. // This handles all arrays, maps, and sets specially, otherwise // it just calls the object's equals() method. public static boolean equals( java.lang.Object obj1, java.lang.Object obj2 ) { // Set of pairs of objects that have been (or are being) considered for // equality. Such pairs are presumed to be equals. If they are not, // this will be detected eventually and the equals method will return // false. Set considered = new HashSet() ; // Map that gives the corresponding component of obj2 for a component // of obj1. This is used to check for the same aliasing and use of // equal objects in both objects. Map counterpart = new IdentityHashMap() ; return equalsHelper( counterpart, considered, obj1, obj2 ) ; } /** If arr1 and arr2 are both arrays of the same component type, * return an array of that component type that consists of the * elements of arr1 followed by the elements of arr2. * Throws IllegalArgumentException otherwise. */ public static Object concatenateArrays( Object arr1, Object arr2 ) { Class comp1 = arr1.getClass().getComponentType() ; Class comp2 = arr2.getClass().getComponentType() ; int len1 = Array.getLength( arr1 ) ; int len2 = Array.getLength( arr2 ) ; if ((comp1 == null) || (comp2 == null)) throw new IllegalStateException( "Arguments must be arrays" ) ; if (!comp1.equals( comp2 )) throw new IllegalStateException( "Arguments must be arrays with the same component type" ) ; Object result = Array.newInstance( comp1, len1 + len2 ) ; int index = 0 ; for (int ctr=0; ctr<len1; ctr++) Array.set( result, index++, Array.get( arr1, ctr ) ) ; for (int ctr=0; ctr<len2; ctr++) Array.set( result, index++, Array.get( arr2, ctr ) ) ; return result ; } //=========================================================================== // Implementation //=========================================================================== private void objectToStringHelper( IdentityHashMap printed, ObjectWriter result, java.lang.Object obj) { if (obj==null) { result.append( "null" ) ; result.endElement() ; } else { Class cls = obj.getClass() ; result.startObject( obj ) ; if (printed.keySet().contains( obj )) { result.endObject( "*VISITED*" ) ; } else { printed.put( obj, null ) ; if (mustUseToString(cls)) { result.endObject( obj.toString() ) ; } else { // First, handle any classes that have special printer // methods defined. This is useful when the class // overrides toString with something that // is not sufficiently detailed. ObjectPrinter printer = (ObjectPrinter)(classToPrinter.get( cls )) ; if (printer != null) { printer.print( printed, result, obj ) ; result.endObject() ; } else { Class compClass = cls.getComponentType() ; if (compClass == null) // handleObject always calls endObject handleObject( printed, result, obj ) ; else { handleArray( printed, result, obj ) ; result.endObject() ; } } } } } } private static interface ObjectPrinter { void print( IdentityHashMap printed, ObjectWriter buff, java.lang.Object obj ) ; } private ObjectPrinter propertiesPrinter = new ObjectPrinter() { public void print( IdentityHashMap printed, ObjectWriter buff, java.lang.Object obj ) { if (!(obj instanceof Properties)) throw new Error() ; Properties props = (Properties)obj ; Enumeration keys = props.propertyNames() ; while (keys.hasMoreElements()) { String key = (String)(keys.nextElement()) ; String value = props.getProperty( key ) ; buff.startElement() ; buff.append( key ) ; buff.append( "=" ) ; buff.append( value ) ; buff.endElement() ; } } } ; private ObjectPrinter collectionPrinter = new ObjectPrinter() { public void print( IdentityHashMap printed, ObjectWriter buff, java.lang.Object obj ) { if (!(obj instanceof Collection)) throw new Error() ; Collection coll = (Collection)obj ; Iterator iter = coll.iterator() ; while (iter.hasNext()) { java.lang.Object element = iter.next() ; buff.startElement() ; objectToStringHelper( printed, buff, element ) ; buff.endElement() ; } } } ; private ObjectPrinter mapPrinter = new ObjectPrinter() { public void print( IdentityHashMap printed, ObjectWriter buff, java.lang.Object obj ) { if (!(obj instanceof Map)) throw new Error() ; Map map = (Map)obj ; Iterator iter = map.entrySet().iterator() ; while (iter.hasNext()) { Entry entry = (Entry)(iter.next()) ; buff.startElement() ; objectToStringHelper( printed, buff, entry.getKey() ) ; buff.append( "=>" ) ; objectToStringHelper( printed, buff, entry.getValue() ) ; buff.endElement() ; } } } ; private static class ClassMap { ArrayList data ; public ClassMap() { data = new ArrayList() ; } /** Return the first element of the ClassMap that is assignable to cls. * The order is determined by the order in which the put method was * called. Returns null if there is no match. */ public java.lang.Object get( Class cls ) { Iterator iter = data.iterator() ; while (iter.hasNext()) { java.lang.Object[] arr = (java.lang.Object[])(iter.next()) ; Class key = (Class)(arr[0]) ; if (key.isAssignableFrom( cls )) return arr[1] ; } return null ; } /** Add obj to the map with key cls. Note that order matters, * as the first match is returned. */ public void put( Class cls, java.lang.Object obj ) { java.lang.Object[] pair = { cls, obj } ; data.add( pair ) ; } } private boolean mustUseToString( Class cls ) { // These probably never occur if (cls.isPrimitive()) return true ; // We must use toString for all primitive wrappers, since // otherwise the code recurses endlessly (access value field // inside Integer, returns another Integer through reflection). if ((cls == Integer.class) || (cls == BigInteger.class) || (cls == BigDecimal.class) || (cls == String.class) || (cls == StringBuffer.class) || (cls == Long.class) || (cls == Short.class) || (cls == Byte.class) || (cls == Character.class) || (cls == Float.class) || (cls == Double.class) || (cls == Boolean.class)) return true ; if (useToString) { try { cls.getDeclaredMethod( "toString", null ) ; return true ; } catch (Exception exc) { return false ; } } return false ; } private void handleObject( IdentityHashMap printed, ObjectWriter result, java.lang.Object obj ) { Class cls = obj.getClass() ; try { Field[] fields; SecurityManager security = System.getSecurityManager(); if (security != null && !Modifier.isPublic(cls.getModifiers())) { fields = new Field[0]; } else { fields = cls.getDeclaredFields(); } for (int ctr=0; ctr<fields.length; ctr++ ) { final Field fld = fields[ctr] ; int modifiers = fld.getModifiers() ; // Do not display field if it is static, since these fields // are always the same for every instances. This could // be made configurable, but I don't think it is // useful to do so. if (!Modifier.isStatic( modifiers )) { if (security != null) { if (!Modifier.isPublic(modifiers)) continue; } result.startElement() ; result.append( fld.getName() ) ; result.append( ":" ) ; try { // Make sure that we can read the field if it is // not public AccessController.doPrivileged( new PrivilegedAction() { public Object run() { fld.setAccessible( true ) ; return null ; } } ) ; java.lang.Object value = fld.get( obj ) ; objectToStringHelper( printed, result, value ) ; } catch (Exception exc2) { result.append( "???" ) ; } result.endElement() ; } } result.endObject() ; } catch (Exception exc2) { result.endObject( obj.toString() ) ; } } private void handleArray( IdentityHashMap printed, ObjectWriter result, java.lang.Object obj ) { Class compClass = obj.getClass().getComponentType() ; if (compClass == boolean.class) { boolean[] arr = (boolean[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == byte.class) { byte[] arr = (byte[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == short.class) { short[] arr = (short[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == int.class) { int[] arr = (int[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == long.class) { long[] arr = (long[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == char.class) { char[] arr = (char[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == float.class) { float[] arr = (float[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else if (compClass == double.class) { double[] arr = (double[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; result.append( arr[ctr] ) ; result.endElement() ; } } else { // array of object java.lang.Object[] arr = (java.lang.Object[])obj ; for (int ctr=0; ctr<arr.length; ctr++) { result.startElement() ; objectToStringHelper( printed, result, arr[ctr] ) ; result.endElement() ; } } } private static class Pair { private java.lang.Object obj1 ; private java.lang.Object obj2 ; Pair( java.lang.Object obj1, java.lang.Object obj2 ) { this.obj1 = obj1 ; this.obj2 = obj2 ; } public boolean equals( java.lang.Object obj ) { if (!(obj instanceof Pair)) return false ; Pair other = (Pair)obj ; return other.obj1 == obj1 && other.obj2 == obj2 ; } public int hashCode() { return System.identityHashCode( obj1 ) ^ System.identityHashCode( obj2 ) ; } } private static boolean equalsHelper( Map counterpart, Set considered, java.lang.Object obj1, java.lang.Object obj2 ) { if ((obj1 == null) || (obj2 == null)) return obj1 == obj2 ; java.lang.Object other2 = counterpart.get( obj1 ) ; if (other2 == null) { other2 = obj2 ; counterpart.put( obj1, other2 ) ; } if (obj1 == other2) return true ; if (obj2 != other2) return false ; Pair pair = new Pair( obj1, obj2 ) ; if (considered.contains( pair )) return true ; else considered.add( pair ) ; if (obj1 instanceof java.lang.Object[] && obj2 instanceof java.lang.Object[]) return equalArrays( counterpart, considered, (java.lang.Object[])obj1, (java.lang.Object[])obj2 ) ; else if (obj1 instanceof Map && obj2 instanceof Map) return equalMaps( counterpart, considered, (Map)obj1, (Map)obj2 ) ; else if (obj1 instanceof Set && obj2 instanceof Set) return equalSets( counterpart, considered, (Set)obj1, (Set)obj2 ) ; else if (obj1 instanceof List && obj2 instanceof List) return equalLists( counterpart, considered, (List)obj1, (List)obj2 ) ; else if (obj1 instanceof boolean[] && obj2 instanceof boolean[]) return Arrays.equals( (boolean[])obj1, (boolean[])obj2 ) ; else if (obj1 instanceof byte[] && obj2 instanceof byte[]) return Arrays.equals( (byte[])obj1, (byte[])obj2 ) ; else if (obj1 instanceof char[] && obj2 instanceof char[]) return Arrays.equals( (char[])obj1, (char[])obj2 ) ; else if (obj1 instanceof double[] && obj2 instanceof double[]) return Arrays.equals( (double[])obj1, (double[])obj2 ) ; else if (obj1 instanceof float[] && obj2 instanceof float[]) return Arrays.equals( (float[])obj1, (float[])obj2 ) ; else if (obj1 instanceof int[] && obj2 instanceof int[]) return Arrays.equals( (int[])obj1, (int[])obj2 ) ; else if (obj1 instanceof long[] && obj2 instanceof long[]) return Arrays.equals( (long[])obj1, (long[])obj2 ) ; else { Class cls = obj1.getClass() ; if (cls != obj2.getClass()) return obj1.equals( obj2 ) ; else return equalsObject( counterpart, considered, cls, obj1, obj2 ) ; } } private static boolean equalsObject( Map counterpart, Set considered, Class cls, java.lang.Object obj1, java.lang.Object obj2 ) { Class objectClass = java.lang.Object.class ; if (cls == objectClass) return true ; Class[] equalsTypes = { objectClass } ; try { Method equalsMethod = cls.getDeclaredMethod( "equals", equalsTypes ) ; return obj1.equals( obj2 ) ; } catch (Exception exc) { if (equalsObjectFields( counterpart, considered, cls, obj1, obj2 )) return equalsObject( counterpart, considered, cls.getSuperclass(), obj1, obj2 ) ; else return false ; } } private static boolean equalsObjectFields( Map counterpart, Set considered, Class cls, java.lang.Object obj1, java.lang.Object obj2 ) { Field[] fields = cls.getDeclaredFields() ; for (int ctr=0; ctr<fields.length; ctr++) { try { final Field field = fields[ctr] ; // Ignore static fields if (!Modifier.isStatic( field.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { field.setAccessible( true ) ; return null ; } } ) ; java.lang.Object value1 = field.get( obj1 ) ; java.lang.Object value2 = field.get( obj2 ) ; if (!equalsHelper( counterpart, considered, value1, value2 )) return false ; } } catch (IllegalAccessException exc) { return false ; } } return true ; } private static boolean equalArrays( Map counterpart, Set considered, java.lang.Object[] arr1, java.lang.Object[] arr2 ) { int len = arr1.length ; if (len != arr2.length) return false ; for (int ctr = 0; ctr<len; ctr++ ) if (!equalsHelper( counterpart, considered, arr1[ctr], arr2[ctr] )) return false ; return true ; } private static boolean equalMaps( Map counterpart, Set considered, Map map1, Map map2 ) { if (map2.size() != map1.size()) return false; try { Iterator i = map1.entrySet().iterator(); while (i.hasNext()) { Entry e = (Entry) i.next(); java.lang.Object key = e.getKey(); java.lang.Object value = e.getValue(); if (value == null) { if (!(map2.get(key)==null && map2.containsKey(key))) return false; } else { if (!equalsHelper( counterpart, considered, value, map2.get(key))) return false; } } } catch(ClassCastException unused) { return false; } catch(NullPointerException unused) { return false; } return true; } // Obviously this is an inefficient quadratic algorithm. // This is taken pretty directly from AbstractSet and AbstractCollection // in the JDK. // For HashSet, an O(n) (with a good hash function) algorithm // is possible, and likewise TreeSet, since it is // ordered, is O(n). But this is not worth the effort here. // Note that the inner loop uses equals, not equalsHelper. // This is needed because of the searching behavior of this test. // However, note that this will NOT correctly handle sets that // contain themselves as members, or that have members that reference // themselves. These cases will cause infinite regress! private static boolean equalSets( Map counterpart, Set considered, Set set1, Set set2 ) { if (set1.size() != set2.size()) return false ; Iterator e1 = set1.iterator() ; while (e1.hasNext()) { java.lang.Object obj1 = e1.next() ; boolean found = false ; Iterator e2 = set2.iterator() ; while (e2.hasNext() && !found) { java.lang.Object obj2 = e2.next() ; found = equals( obj1, obj2 ) ; } if (!found) return false ; } return true ; } private static boolean equalLists( Map counterpart, Set considered, List list1, List list2 ) { ListIterator e1 = list1.listIterator(); ListIterator e2 = list2.listIterator(); while(e1.hasNext() && e2.hasNext()) { java.lang.Object o1 = e1.next(); java.lang.Object o2 = e2.next(); if (!(o1==null ? o2==null : equalsHelper( counterpart, considered, o1, o2))) return false; } return !(e1.hasNext() || e2.hasNext()); } }