package Sequenic.T2.Obj; /** * A utility to show (print) the content of an object. * *

The show recursively traverses the structure of the * object. Circular pointer will be marked. A maximum depth (of the * traversal) can also be specified. * *

Numbers will be associated with the shown object and all its * subobject, so that we can indicate if a (sub-) object points to an * object that has been shown. It is possible to reset the numbering * at each call to {@link Sequenic.T2.Obj#Show show} or to keep the * numbering {@link Sequenic.T2.Obj#showWithContNum accross multiple calls}. * *

Note: showing primitive typed and boxing typed values is a * problem. We use reflection to obtain the types of the fields, but * typically reflection will say that a primitive typed field to have * the corresponding boxing class. So we can't see the difference * between the two. Currently we'll just omit the numbering * information on these kind of values, since they will falsely signal * for aliasing. */ import java.lang.reflect.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.io.*; import Sequenic.T2.Obj.XPP; // import Sequenic.T2.examples.* ; public class XShow { /** * As {@link Sequenic.T2.Obj said}, objects visited during the show will * be numbered. This numbering is maintain by this pool. */ private Map pool = new IdentityHashMap(); /** * Show will only go down up to this maximum depth of object * structure. So, subobjects at the deeper depth will not be * shown. The default is 5. */ public int maxDepth = 5; /** * The initial indentation. Default is 6. */ public int InitialIndentation = 6; /** * Turn this to true to enable internal assertions. */ static public boolean Debug = false; /** * Let C be a class. If (C,fns) is in this map, then when showing * instances of C, only fields named in fns will be showed. * *

Add entries to this variable to have a class with lots of fields * shown more compactly. */ static public Map> showFilter = new HashMap>(); /** * When a class C is listed here, then when showing an instance of * C we will only show its name (its internal state will not be * shown at all). * *

Add classes to this variable to surpress showing their state. */ static public List veryCompactShow; // Class initialization: static { //System.out.println("+++ initialization of the class XShow") ; veryCompactShow = new LinkedList(); veryCompactShow.add(Random.class); veryCompactShow.add(Reader.class); veryCompactShow.add(Writer.class); veryCompactShow.add(InputStream.class); veryCompactShow.add(OutputStream.class); veryCompactShow.add(RandomAccessFile.class); veryCompactShow.add(File.class) ; veryCompactShow.add(Class.class) ; veryCompactShow.add(XShow.class) ; //for (Class D : veryCompactShow) System.out.println("+++ D : " + D) ; } public XShow() { } /** * Create a new Shower object, with the specified maximum show * depth and initial indentation. */ public XShow(int maxdepth, int initialIndent) { //assert maxdepth >= 0 && initialIndent >= 0 : "PRE" ; maxDepth = maxdepth; InitialIndentation = initialIndent; } private static String[] boxingTypeNames = { "java.lang.Byte", "java.lang.Integer", "java.lang.Long", "java.lang.Float", "java.lang.Double", "java.lang.Boolean", "java.lang.Character" }; private static boolean isBoxingType(Class C) { boolean found = false; for (int i = 0; i < boxingTypeNames.length && !found; i++) { found = C.getName().equals(boxingTypeNames[i]); } return found; } /** * Show the object o. The object numbering is maintained across * multiple calls to this method. */ public String showWithContNum(Object o) { LinkedList visited = new LinkedList(); return showWorker(o, visited, new XPP(), maxDepth).render(InitialIndentation); } /** * Show the object o. The object numbering is reset at each call. * * @param indent Initial indentiation. */ public static String show(Object o, int indent, int maxDepth) { XShow s = new XShow(maxDepth, indent); return s.showWithContNum(o); } /** * Show an object. Max-depth is 5. Initial indentation is * 6. Object numbering is not continued over multiple calls. */ static public String show(Object o) { //try { return show(o, 6, 5); //} //catch (NullPointerException e){ // System.out.println("+++ " + e + ", ") ; // e.printStackTrace() ; // throw e ; //} } // just a method to check if an object is a collection: private static boolean isCollection(Object o) { return Collection.class.isInstance(o) ; } /** * To obtain all non-interface and non-abstract superclasses of a given * class. The class "Object" is not included in the returned list. */ private static List getAllSuperClasses(Class C) { List collected = new LinkedList(); if (// C.isInterface() || --> actually unfeasible //Modifier.isAbstract(C.getModifiers()) || C.getName().equals("java.lang.Object")) { return collected; } collected.add(C); collected.addAll(getAllSuperClasses(C.getSuperclass())); return collected; } /** * Get ALL fields of a class. This includes private fields, and fields * of the superclasses. Fields from class Objects will be excluded. */ private static List getAllFields(Class C) { List result = new LinkedList() ; List ancestors = getAllSuperClasses(C) ; Field[] fs = null ; int i = 0 ; for (Class D : ancestors) { fs = D.getDeclaredFields() ; for (i=0; i= 0; String s = "+++ " + (depth >= 0) ; } if (depth <= 0) { return previousPP.aside_(XPP.text("...")); } if (o == null) { return previousPP.aside_(XPP.text("NULL")); } Class C = o.getClass(); // primitive type: if (// C.isPrimitive() || --> actually unfeasible C.isEnum() || C.getName().equals("java.lang.String")) { return previousPP.aside_(XPP.text("(" + C.getSimpleName() + ") : " + o)); } // Bunch of standard Classes with lots of internal info, // they will only be displayed abstractly: // REMOVED. Now all handled in the same way using the "veryCompactShow" // mechanism: //if (C.getName().equals("java.lang.Class")) { // String name = ((Class) o).getName(); // return previousPP.aside_(PP.text("(" + C.getSimpleName() + ") : " + name)); //} //if (File.class.isAssignableFrom(C)) { // String name = ((File) o).getName(); // return previousPP.aside_(PP.text("(" + File.class.getSimpleName() + ") : " + name)); //} // Handle class with compacted show: //int k = 0 ; for(Class D : veryCompactShow) { //if (D==null) System.out.println("+++ " + k + "-th D is null!") ; //k++ ; if (D.isAssignableFrom(C)) { return previousPP.aside_(XPP.text("(" + C.getSimpleName() + ") : ...")); } } // else: boolean wasInPool = pool.containsKey(o); if (!wasInPool) { // o has not been taken in the pool int newIndex = pool.size(); pool.put(o, newIndex); } int indexOf_o = pool.get(o); boolean hasBeenVisited = visited.contains(o); if (!hasBeenVisited) { visited.add(o); } else return previousPP.aside_(XPP.text("(" + C.getSimpleName() + ") ---> @" + indexOf_o)); if (Debug) { // assert visited.contains(o); String s = "+++" + visited.contains(o); } // Array : int i = 0; if (C.isArray()) { XPP arrayPP = XPP.text("(ARRAY) @ " + indexOf_o); for (i = 0; i < Array.getLength(o); i++) { // System.out.println("@" + i) ; arrayPP.ontop(showWorker(Array.get(o, i), visited, XPP.text("[" + i + "]"), depth - 1)); } return previousPP.aside_(arrayPP); } // Collection: if (isCollection(o)) { XPP colPP = XPP.text("(" + C.getSimpleName() + ") @ " + indexOf_o); i = 0; for (Object element : (Collection) o) { colPP.ontop(showWorker(element, visited, XPP.text("[" + i + "]"), depth - 1)); i++; } return previousPP.aside_(colPP); } // if the object is not array nor collection: // Box types : if (isBoxingType(C)) { return previousPP.aside_(XPP.text("(" + C.getSimpleName() + ")" // ") @ " // + indexOf_o + " : " + o)); } // o is of other types, and has not been visited: // getting all C's fields, including those declared by superclasses, // and we first remove those fields which have been specified not // to be shown: List allfields = getAllFields(C); List tobeRemoved = new LinkedList(); List onlyShowTheseFields = showFilter.get(C) ; for (Field f : allfields) { f.setAccessible(true); if (f.getName().equals("$assertionsDisabled")) { tobeRemoved.add(f); } if (onlyShowTheseFields == null) continue ; boolean found = false ; for (String fn : onlyShowTheseFields) { if (f.getName().equals(fn)) { found = true ; break ; } } if (!found) tobeRemoved.add(f); } for (Field f : tobeRemoved) { allfields.remove(f); } if (Debug) { //assert !hasBeenVisited; String s = "+++" + !hasBeenVisited; } XPP titleLine = XPP.text("(" + C.getName() + ") @ " + indexOf_o); if (allfields.isEmpty()) { return previousPP.aside_(titleLine); } String fname; // PP pp_fields = previousPP.aside_(titleLine) ; XPP pp_fields = titleLine; XPP entry = null; Object fieldval = null; i = 0; for (Field field : allfields) { try { fieldval = field.get(o); entry = XPP.text("" + field.getName()); //System.out.println(">>> " + C + "#" + field.getName() + ", declared by " + field.getDeclaringClass()) ; if (field.getDeclaringClass() != C) { entry = XPP.text("" + field.getDeclaringClass().getSimpleName() + "." + field.getName()); } pp_fields.ontop(showWorker(fieldval, visited, entry, depth - 1)); } catch (IllegalAccessException ex) { ex.printStackTrace(System.err); // not feasible } catch (IllegalArgumentException ex) { ex.printStackTrace(System.err); // not feasible } } return previousPP.aside_(pp_fields); } }