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.io.*; import Sequenic.P2.*; import Sequenic.T2.Msg.*; import Sequenic.T2.Engines.Util ; // import Sequenic.T2.examples.* ; public class Show { /** * As {@link Sequenic.T2.Obj said}, objects visited during the show will * be numbered. This numbering is maintain by this pool. */ protected IdentityHashMap 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 { 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) ; } public Show() { } /** * Create a new Shower object, with the specified maximum show * depth and initial indentation. */ public Show(int maxdepth, int initialIndent) { assert maxdepth >= 0 && initialIndent >= 0 : "PRE" ; maxDepth = maxdepth; InitialIndentation = initialIndent; } /** * An auxilliary variable to freeze pool. */ private HashMap aux_pool_frozen = new HashMap(); ; private void save_pool() { Set keys = pool.keySet(); for (Object x : keys) { if (!aux_pool_frozen.containsKey(x)) { aux_pool_frozen.put(x, pool.get(x)); } } } private boolean maintainMapping() { Set keys = aux_pool_frozen.keySet(); boolean ok = true; for (Object x : keys) { ok = aux_pool_frozen.get(x) == pool.get(x); if (!ok) { break; } } return ok; } /** * Class invariant, saying that the mapping in the pool is preserved. * Note that this is a temporal property. */ public boolean classinv() { boolean ok = maintainMapping(); save_pool(); return ok; } 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 PP(), maxDepth).render(InitialIndentation); } /** * Specification of showWithContNum. The spec is actually encoded as * assertions in the body of showWorker. They are turned-on by switching * on the SelfTest flag. */ public String showWithContNum_spec(Object o) { // Debug = true; // Turn-on self-test flag (for this object only) String result = showWithContNum(o); return result; } /** * Reset the continous numbering. */ public void resetContinousNumbering() { pool.clear(); aux_pool_frozen.clear(); } /** * 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) { Show s = new Show(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) { return show(o, 6, 5); } // just a method to check if an object is a collection: private static boolean isCollection(Object o) { try { return Class.forName("java.util.Collection").isInstance(o); } catch (Exception e) { } return false; } /** * 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 = Util.getAllSuperClasses(C) ; Field[] fs = null ; int i = 0 ; for (Class D : ancestors) { fs = D.getDeclaredFields() ; for (i=0; i= 0; } if (depth <= 0) { return previousPP.aside_(PP.text("...")); } if (o == null) { return previousPP.aside_(PP.text("NULL")); } Class C = o.getClass(); // primitive type: if (C.isPrimitive() || C.isEnum() || C.getName().equals("java.lang.String")) { return previousPP.aside_(PP.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: for(Class D : veryCompactShow) { if (D.isAssignableFrom(C)) { return previousPP.aside_(PP.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); } if (Debug) { assert visited.contains(o); // Array : } int i = 0; if (C.isArray()) { PP arrayPP = PP.text("(ARRAY) @ " + indexOf_o); for (i = 0; i < Array.getLength(o); i++) { // System.out.println("@" + i) ; arrayPP.ontop(showWorker(Array.get(o, i), visited, PP.text("[" + i + "]"), depth - 1)); } return previousPP.aside_(arrayPP); //Message.console(Message.DEVEL_WARNING,"Cannot show an array.",new Show()) ; //return PP.aside(previousPP, PP.text("...some array")) ; } // Collection: if (isCollection(o)) { PP colPP = PP.text("(" + C.getSimpleName() + ") @ " + indexOf_o); i = 0; for (Object element : (Collection) o) { colPP.ontop(showWorker(element, visited, PP.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_(PP.text("(" + C.getSimpleName() + ")" // ") @ " // + indexOf_o + " : " + o)); } // else o is an object with fields : if (hasBeenVisited) { return previousPP.aside_(PP.text("(" + C.getSimpleName() + ") ---> @" + indexOf_o)); } // o 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; } PP titleLine = PP.text("(" + C.getName() + ") @ " + indexOf_o); if (allfields.isEmpty()) { return previousPP.aside_(titleLine); } String fname; // PP pp_fields = previousPP.aside_(titleLine) ; PP pp_fields = titleLine; PP entry = null; Object fieldval = null; i = 0; for (Field field : allfields) { try { fieldval = field.get(o); entry = PP.text("" + field.getName()); if (field.getDeclaringClass() != C) { entry = PP.text("" + field.getDeclaringClass().getSimpleName() + "." + field.getName()); } pp_fields.ontop(showWorker(fieldval, visited, entry, depth - 1)); } catch (IllegalAccessException ex) { ex.printStackTrace(System.out); } catch (IllegalArgumentException ex) { ex.printStackTrace(System.out); } } return previousPP.aside_(pp_fields); } // test: /* static public void main(String[] args) { System.out.println(show(new Integer(100))) ; System.out.println(show("Hello ET!")) ; System.out.println(show(new int[2])) ; // System.out.println(show(new B3())) ; //MyList xs = new MyList() ; //xs.insert(1) ; xs.insert(-1) ; //System.out.println(show(xs)) ; //xs = new MyList() ; //xs.insert(0) ; xs.list.next = xs.list ; //System.out.println(show(xs)) ; } */ }