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)) ;
}
*/
}