duke@1: /* ohair@158: * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as ohair@158: * published by the Free Software Foundation. Oracle designates this duke@1: * particular file as subject to the "Classpath" exception as provided ohair@158: * by Oracle in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * ohair@158: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@158: * or visit www.oracle.com if you need additional information or have any ohair@158: * questions. duke@1: */ duke@1: duke@1: package com.sun.corba.se.impl.orbutil; duke@1: duke@1: import java.security.PrivilegedAction; duke@1: import java.security.AccessController; duke@1: import java.util.ArrayList; duke@1: import java.util.Arrays; duke@1: import java.util.Map; duke@1: import java.util.List; duke@1: import java.util.ListIterator; duke@1: import java.util.Set; duke@1: import java.util.Map.Entry; duke@1: import java.util.Collection; duke@1: import java.util.HashMap; duke@1: import java.util.HashSet; duke@1: import java.util.Hashtable; duke@1: import java.util.Iterator; duke@1: import java.util.Enumeration; duke@1: import java.util.Properties; duke@1: import java.util.IdentityHashMap; duke@1: import java.lang.reflect.Array; duke@1: import java.lang.reflect.Field; duke@1: import java.lang.reflect.Method; duke@1: import java.lang.reflect.Modifier; duke@1: import java.math.BigInteger ; duke@1: import java.math.BigDecimal ; duke@1: duke@1: public final class ObjectUtility { duke@1: private boolean useToString ; duke@1: private boolean isIndenting ; duke@1: private int initialLevel ; duke@1: private int increment ; duke@1: private ClassMap classToPrinter = new ClassMap() ; duke@1: duke@1: private static ObjectUtility standard = new ObjectUtility( false, true, duke@1: 0, 4 ) ; duke@1: private static ObjectUtility compact = new ObjectUtility( true, false, duke@1: 0, 4 ) ; duke@1: duke@1: private ObjectUtility( boolean useToString, boolean isIndenting, duke@1: int initialLevel, int increment ) duke@1: { duke@1: this.useToString = useToString ; duke@1: this.isIndenting = isIndenting ; duke@1: this.initialLevel = initialLevel ; duke@1: this.increment = increment ; duke@1: classToPrinter.put( Properties.class, propertiesPrinter ) ; duke@1: classToPrinter.put( Collection.class, collectionPrinter ) ; duke@1: classToPrinter.put( Map.class, mapPrinter ) ; duke@1: } duke@1: duke@1: /** Construct an Utility instance with the desired objectToString duke@1: * behavior. duke@1: */ duke@1: public static ObjectUtility make( boolean useToString, boolean isIndenting, duke@1: int initialLevel, int increment ) duke@1: { duke@1: return new ObjectUtility( useToString, isIndenting, initialLevel, duke@1: increment ) ; duke@1: } duke@1: duke@1: /** Construct an Utility instance with the desired objectToString duke@1: * behavior. duke@1: */ duke@1: public static ObjectUtility make( boolean useToString, boolean isIndenting ) duke@1: { duke@1: return new ObjectUtility( useToString, isIndenting, 0, 4 ) ; duke@1: } duke@1: duke@1: /** Get the standard Utility object that supports objectToString with duke@1: * indented display and no use of toString() methods. duke@1: */ duke@1: public static ObjectUtility make() duke@1: { duke@1: return standard ; duke@1: } duke@1: duke@1: /** A convenience method that gives the default behavior: use indenting duke@1: * to display the object's structure and do not use built-in toString duke@1: * methods. duke@1: */ duke@1: public static String defaultObjectToString( java.lang.Object object ) duke@1: { duke@1: return standard.objectToString( object ) ; duke@1: } duke@1: duke@1: public static String compactObjectToString( java.lang.Object object ) duke@1: { duke@1: return compact.objectToString( object ) ; duke@1: } duke@1: duke@1: /** objectToString handles display of arbitrary objects. It correctly duke@1: * handles objects whose elements form an arbitrary graph. It uses duke@1: * reflection to display the contents of any kind of object. duke@1: * An object's toString() method may optionally be used, but the default duke@1: * is to ignore all toString() methods except for those defined for duke@1: * primitive types, primitive type wrappers, and strings. duke@1: */ duke@1: public String objectToString(java.lang.Object obj) duke@1: { duke@1: IdentityHashMap printed = new IdentityHashMap() ; duke@1: ObjectWriter result = ObjectWriter.make( isIndenting, initialLevel, duke@1: increment ) ; duke@1: objectToStringHelper( printed, result, obj ) ; duke@1: return result.toString() ; duke@1: } duke@1: duke@1: // Perform a deep structural equality comparison of the two objects. duke@1: // This handles all arrays, maps, and sets specially, otherwise duke@1: // it just calls the object's equals() method. duke@1: public static boolean equals( java.lang.Object obj1, java.lang.Object obj2 ) duke@1: { duke@1: // Set of pairs of objects that have been (or are being) considered for duke@1: // equality. Such pairs are presumed to be equals. If they are not, duke@1: // this will be detected eventually and the equals method will return duke@1: // false. duke@1: Set considered = new HashSet() ; duke@1: duke@1: // Map that gives the corresponding component of obj2 for a component duke@1: // of obj1. This is used to check for the same aliasing and use of duke@1: // equal objects in both objects. duke@1: Map counterpart = new IdentityHashMap() ; duke@1: duke@1: return equalsHelper( counterpart, considered, obj1, obj2 ) ; duke@1: } duke@1: duke@1: /** If arr1 and arr2 are both arrays of the same component type, duke@1: * return an array of that component type that consists of the duke@1: * elements of arr1 followed by the elements of arr2. duke@1: * Throws IllegalArgumentException otherwise. duke@1: */ duke@1: public static Object concatenateArrays( Object arr1, Object arr2 ) duke@1: { duke@1: Class comp1 = arr1.getClass().getComponentType() ; duke@1: Class comp2 = arr2.getClass().getComponentType() ; duke@1: int len1 = Array.getLength( arr1 ) ; duke@1: int len2 = Array.getLength( arr2 ) ; duke@1: duke@1: if ((comp1 == null) || (comp2 == null)) duke@1: throw new IllegalStateException( "Arguments must be arrays" ) ; duke@1: if (!comp1.equals( comp2 )) duke@1: throw new IllegalStateException( duke@1: "Arguments must be arrays with the same component type" ) ; duke@1: duke@1: Object result = Array.newInstance( comp1, len1 + len2 ) ; duke@1: duke@1: int index = 0 ; duke@1: duke@1: for (int ctr=0; ctr" ) ; duke@1: objectToStringHelper( printed, buff, entry.getValue() ) ; duke@1: buff.endElement() ; duke@1: } duke@1: } duke@1: } ; duke@1: duke@1: private static class ClassMap { duke@1: ArrayList data ; duke@1: duke@1: public ClassMap() duke@1: { duke@1: data = new ArrayList() ; duke@1: } duke@1: duke@1: /** Return the first element of the ClassMap that is assignable to cls. duke@1: * The order is determined by the order in which the put method was duke@1: * called. Returns null if there is no match. duke@1: */ duke@1: public java.lang.Object get( Class cls ) duke@1: { duke@1: Iterator iter = data.iterator() ; duke@1: while (iter.hasNext()) { duke@1: java.lang.Object[] arr = (java.lang.Object[])(iter.next()) ; duke@1: Class key = (Class)(arr[0]) ; duke@1: if (key.isAssignableFrom( cls )) duke@1: return arr[1] ; duke@1: } duke@1: duke@1: return null ; duke@1: } duke@1: duke@1: /** Add obj to the map with key cls. Note that order matters, duke@1: * as the first match is returned. duke@1: */ duke@1: public void put( Class cls, java.lang.Object obj ) duke@1: { duke@1: java.lang.Object[] pair = { cls, obj } ; duke@1: data.add( pair ) ; duke@1: } duke@1: } duke@1: duke@1: private boolean mustUseToString( Class cls ) duke@1: { duke@1: // These probably never occur duke@1: if (cls.isPrimitive()) duke@1: return true ; duke@1: duke@1: // We must use toString for all primitive wrappers, since duke@1: // otherwise the code recurses endlessly (access value field duke@1: // inside Integer, returns another Integer through reflection). duke@1: if ((cls == Integer.class) || duke@1: (cls == BigInteger.class) || duke@1: (cls == BigDecimal.class) || duke@1: (cls == String.class) || duke@1: (cls == StringBuffer.class) || duke@1: (cls == Long.class) || duke@1: (cls == Short.class) || duke@1: (cls == Byte.class) || duke@1: (cls == Character.class) || duke@1: (cls == Float.class) || duke@1: (cls == Double.class) || duke@1: (cls == Boolean.class)) duke@1: return true ; duke@1: duke@1: if (useToString) { duke@1: try { jjg@173: cls.getDeclaredMethod( "toString", (Class[])null ) ; duke@1: return true ; duke@1: } catch (Exception exc) { duke@1: return false ; duke@1: } duke@1: } duke@1: duke@1: return false ; duke@1: } duke@1: duke@1: private void handleObject( IdentityHashMap printed, ObjectWriter result, duke@1: java.lang.Object obj ) duke@1: { duke@1: Class cls = obj.getClass() ; duke@1: duke@1: try { duke@1: Field[] fields; duke@1: SecurityManager security = System.getSecurityManager(); duke@1: if (security != null && !Modifier.isPublic(cls.getModifiers())) { duke@1: fields = new Field[0]; duke@1: } else { duke@1: fields = cls.getDeclaredFields(); duke@1: } duke@1: duke@1: for (int ctr=0; ctr