Created a visualization that displays classes, their public fields,

members and constructors rather than the inheritance hierarchy.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@488 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2001-12-01 06:22:18 +00:00
parent d6758265bf
commit ecbf77bebd
5 changed files with 720 additions and 6 deletions
@@ -1,5 +1,5 @@
//
// $Id: Driver.java,v 1.13 2001/12/01 05:28:01 mdb Exp $
// $Id: Driver.java,v 1.14 2001/12/01 06:22:18 mdb Exp $
//
// viztool - a tool for visualizing collections of java classes
// Copyright (C) 2001 Michael Bayne
@@ -70,8 +70,8 @@ public class Driver
// and finally generate the visualization
PackageEnumerator penum = new PackageEnumerator(pkgroot, enum, true);
Visualizer viz = new HierarchyVisualizer(pkgroot, penum);
// Visualizer viz = new SummaryVisualizer(pkgroot, penum);
// Visualizer viz = new HierarchyVisualizer(pkgroot, penum);
Visualizer viz = new SummaryVisualizer(pkgroot, penum);
if (print) {
// we use the print system to render things
@@ -0,0 +1,382 @@
//
// $Id: ClassSummary.java,v 1.1 2001/12/01 06:22:18 mdb Exp $
//
// viztool - a tool for visualizing collections of java classes
// Copyright (C) 2001 Michael Bayne
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This program 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 for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.viztool.summary;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import com.samskivert.viztool.Log;
import com.samskivert.viztool.layout.Element;
import com.samskivert.viztool.util.FontPicker;
import com.samskivert.viztool.util.LayoutUtil;
import com.samskivert.viztool.util.RenderUtil;
/**
* A class summary displays information about a particular class
* (specifically, the interfaces it implements, the class it extends, its
* public instances, member functions, and inner class definitions).
*/
public class ClassSummary implements Element
{
/**
* Constructs a class summary for the specified class.
*/
public ClassSummary (Class subject, SummaryVisualizer viz)
{
_viz = viz;
_subject = subject;
_name = _viz.name(_subject);
// obtain information on our subject class
Class parent = _subject.getSuperclass();
if (parent != null && !parent.equals(Object.class)) {
_parentName = _viz.name(parent);
}
// get the implemented interfaces
Class[] interfaces = _subject.getInterfaces();
int icount = interfaces.length;
_interfaces = new String[icount];
for (int i = 0; i < icount; i++) {
_interfaces[i] = _viz.name(interfaces[i]);
}
// get the public fields
Field[] fields = _subject.getDeclaredFields();
ArrayList fsigtypes = new ArrayList();
ArrayList fsigs = new ArrayList();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
if ((f.getModifiers() & Modifier.PUBLIC) != 0) {
fsigtypes.add(genFieldTypeSig(f));
fsigs.add(f.getName());
}
}
_fieldTypes = new String[fsigtypes.size()];
fsigtypes.toArray(_fieldTypes);
_fields = new String[fsigs.size()];
fsigs.toArray(_fields);
// get the public constructors and methods
ArrayList sigrets = new ArrayList();
ArrayList sigs = new ArrayList();
Constructor[] ctors = _subject.getConstructors();
for (int i = 0; i < ctors.length; i++) {
Constructor c = ctors[i];
if ((c.getModifiers() & Modifier.PUBLIC) != 0) {
sigrets.add(" ");
sigs.add(genConstructorSig(c));
}
}
Method[] methods = _subject.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if ((m.getModifiers() & Modifier.PUBLIC) != 0) {
sigrets.add(genMethodRetSig(m));
sigs.add(genMethodSig(m));
}
}
_methodReturns = new String[sigrets.size()];
sigrets.toArray(_methodReturns);
_methods = new String[sigs.size()];
sigs.toArray(_methods);
}
/**
* Introspects on our subject class and determines how much space
* we'll need to visualize it.
*
* @param gfx the graphics context in which this summary will
* eventually be rendered.
*/
public void layout (Graphics2D gfx)
{
FontRenderContext frc = gfx.getFontRenderContext();
// the header will be the name of this class surrounded by N
// points of space and a box
Rectangle2D bounds = LayoutUtil.getTextBox(
_subject.isInterface() ? FontPicker.getInterfaceFont() :
FontPicker.getClassFont(), frc, _name);
double spacing = 0;
// add our parent class if we've got one
if (_parentName != null) {
String subtext = "extends " + _parentName;
bounds = LayoutUtil.accomodate(
bounds, FontPicker.getDeclaresFont(), frc,
LayoutUtil.SUBORDINATE_INSET, subtext);
}
// add our interfaces
bounds = LayoutUtil.accomodate(
bounds, FontPicker.getImplementsFont(), frc,
LayoutUtil.SUBORDINATE_INSET, _interfaces);
// add our fields
bounds = LayoutUtil.accomodate(
bounds, FontPicker.getClassFont(), frc, 0, _fieldTypes, _fields);
spacing += (_fields.length > 0) ? 2*LayoutUtil.HEADER_BORDER : 0;
// add our constructors and methods
bounds = LayoutUtil.accomodate(
bounds, FontPicker.getClassFont(), frc, 0,
_methodReturns, _methods);
spacing += (_methods.length > 0) ? 2*LayoutUtil.HEADER_BORDER : 0;
// incorporate space for the gaps
bounds.setRect(bounds.getX(), bounds.getY(), bounds.getWidth(),
bounds.getHeight() + spacing);
// grab the new bounds
_bounds = bounds;
}
/**
* Renders this class summary to the specified graphics context.
*/
public void render (Graphics2D gfx)
{
// figure out where we'll be rendering
Rectangle2D bounds = getBounds();
double x = bounds.getX() + LayoutUtil.HEADER_BORDER;
double y = bounds.getY() + LayoutUtil.HEADER_BORDER;
double maxwid = 0, sy1 = 0, sy2 = 0;
// draw the name
FontRenderContext frc = gfx.getFontRenderContext();
Font font = _subject.isInterface() ?
FontPicker.getInterfaceFont() : FontPicker.getClassFont();
Rectangle2D bnds =
RenderUtil.renderString(gfx, frc, font, x, y, _name);
maxwid = Math.max(maxwid, bnds.getWidth() +
2*LayoutUtil.HEADER_BORDER);
y += bnds.getHeight();
// render the parent classname
if (_parentName != null) {
bnds = RenderUtil.renderString(
gfx, frc, FontPicker.getDeclaresFont(),
x + LayoutUtil.SUBORDINATE_INSET, y, _parentName);
maxwid = Math.max(maxwid, bnds.getWidth() +
2*LayoutUtil.HEADER_BORDER +
LayoutUtil.SUBORDINATE_INSET);
y += bnds.getHeight();
}
// render our implemented interfaces
bnds = RenderUtil.renderStrings(
gfx, frc, FontPicker.getImplementsFont(),
x + LayoutUtil.SUBORDINATE_INSET, y, _interfaces);
maxwid = Math.max(maxwid, bnds.getWidth() +
2*LayoutUtil.HEADER_BORDER +
LayoutUtil.SUBORDINATE_INSET);
y += bnds.getHeight();
// stroke a box that contains the header
Rectangle2D outline = new Rectangle2D.Double(
bounds.getX(), bounds.getY(),
maxwid, y + LayoutUtil.HEADER_BORDER - bounds.getY());
gfx.draw(outline);
// leave space for a separator
if (_fields.length > 0) {
y += LayoutUtil.HEADER_BORDER;
sy1 = y;
y += LayoutUtil.HEADER_BORDER;
}
// render our fields
bnds = RenderUtil.renderStrings(
gfx, frc, FontPicker.getClassFont(), x, y, _fieldTypes, _fields);
maxwid = Math.max(maxwid, bnds.getWidth() +
2*LayoutUtil.HEADER_BORDER);
y += bnds.getHeight();
// leave space for a separator
if (_methods.length > 0) {
y += LayoutUtil.HEADER_BORDER;
sy2 = y;
y += LayoutUtil.HEADER_BORDER;
}
// render our constructors and methods
bnds = RenderUtil.renderStrings(
gfx, frc, FontPicker.getClassFont(), x, y,
_methodReturns, _methods);
maxwid = Math.max(maxwid, bnds.getWidth() +
2*LayoutUtil.HEADER_BORDER);
y += bnds.getHeight();
// draw our separators now that we know how wide things are
double x1 = bounds.getX(), x2 = x1 + maxwid;
if (sy1 > 0) {
gfx.draw(new Line2D.Double(x1, sy1, x2, sy1));
}
if (sy2 > 0) {
gfx.draw(new Line2D.Double(x1, sy2, x2, sy2));
}
}
// documentation inherited
public String getName ()
{
return _name;
}
// documentation inherited
public Rectangle2D getBounds ()
{
return _bounds;
}
// documentation inherited
public void setBounds (double x, double y, double width, double height)
{
_bounds.setRect(x, y, width, height);
}
/**
* Generates a signature for the type of the supplied field.
*/
public String genFieldTypeSig (Field field)
{
StringBuffer buf = new StringBuffer();
if ((field.getModifiers() & Modifier.STATIC) != 0) {
buf.append("static ");
}
buf.append(_viz.name(field.getType())).append(" ");
return buf.toString();
}
/**
* Generates a signature for the supplied constructor.
*/
public String genConstructorSig (Constructor ctor)
{
StringBuffer buf = new StringBuffer();
buf.append(_viz.name(ctor.getDeclaringClass())).append("(");
Class[] ptypes = ctor.getParameterTypes();
for (int i = 0; i < ptypes.length; i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(_viz.name(ptypes[i]));
}
buf.append(")");
Class[] etypes = ctor.getExceptionTypes();
if (etypes.length > 0) {
buf.append(" throws ");
for (int i = 0; i < etypes.length; i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(_viz.name(etypes[i]));
}
}
return buf.toString();
}
/**
* Generates a signature for the return value of the supplied method.
*/
public String genMethodRetSig (Method method)
{
StringBuffer buf = new StringBuffer();
if ((method.getModifiers() & Modifier.STATIC) != 0) {
buf.append("static ");
}
buf.append(_viz.name(method.getReturnType()));
return buf.toString();
}
/**
* Generates a signature for the supplied method (minus return type).
*/
public String genMethodSig (Method method)
{
StringBuffer buf = new StringBuffer();
buf.append(method.getName()).append("(");
Class[] ptypes = method.getParameterTypes();
for (int i = 0; i < ptypes.length; i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(_viz.name(ptypes[i]));
}
buf.append(")");
Class[] etypes = method.getExceptionTypes();
if (etypes.length > 0) {
buf.append(" throws ");
for (int i = 0; i < etypes.length; i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(_viz.name(etypes[i]));
}
}
return buf.toString();
}
/** The package we're visualizing, which we'll strip from the front of
* class names. */
protected SummaryVisualizer _viz;
/** The class for which we're generating a summary visualization. */
protected Class _subject;
/** The cleaned up name of the class we're summarizing. */
protected String _name;
/** The name of our parent class or null if we don't have an
* interesting parent class. */
protected String _parentName;
/** The names of interfaces that we implement. */
protected String[] _interfaces;
/** The types of our public fields. */
protected String[] _fieldTypes;
/** The names of our public fields. */
protected String[] _fields;
/** The return types of our public constructors and methods. */
protected String[] _methodReturns;
/** The signatures of our public constructors and methods (minus
* return type). */
protected String[] _methods;
/** Our bounds. */
protected Rectangle2D _bounds = new Rectangle2D.Double();
}
@@ -0,0 +1,245 @@
//
// $Id: SummaryVisualizer.java,v 1.1 2001/12/01 06:22:18 mdb Exp $
//
// viztool - a tool for visualizing collections of java classes
// Copyright (C) 2001 Michael Bayne
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This program 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 for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.viztool.summary;
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.util.ArrayList;
import java.util.Iterator;
import com.samskivert.viztool.Log;
import com.samskivert.viztool.Visualizer;
import com.samskivert.viztool.layout.ElementLayout;
import com.samskivert.viztool.layout.PackedColumnElementLayout;
/**
* The summary visualizer displays summaries of the enumerated classes.
*/
public class SummaryVisualizer implements Visualizer
{
/**
* Constructs a summary visualizer with the supplied enumerator as its
* source of classes. If the summary enumerator should be limited to a
* particular set of classes (and it most likely should), a filter
* enumerator should be supplied that returns only the classes to be
* visualized.
*
* @param pkgroot The name of the top-level package in which the
* visualized classes reside. This will be used to shorten the package
* names that are displayed.
* @param iter The enumerator that will return the names of the
* classes for which visualization is desired.
*/
public SummaryVisualizer (String pkgroot, Iterator iter)
{
// keep track of the package root
_pkgroot = pkgroot;
// create our class summaries
while (iter.hasNext()) {
// strip out inner classes, we'll catch those via their
// declaring classes
String name = (String)iter.next();
if (name.indexOf("$") != -1) {
continue;
}
// add a class summary instance for this class
try {
Class subject = Class.forName(name);
_summaries.add(new ClassSummary(subject, this));
} catch (Exception e) {
Log.warning("Unable to introspect class [class=" + name +
", error=" + e + "].");
}
}
}
/**
* Configures the visualizer to prefix classnames with their package
* names (when they are outside of the visualized package root) or
* not.
*/
public void setDisplayPackageNames (boolean displayPackageNames)
{
_displayPackageNames = displayPackageNames;
}
/**
* Lays out the class summary visualizations into the specified page
* dimensions.
*/
public void layout (Graphics2D gfx, double x, double y,
double width, double height)
{
// first layout all of our summaries (giving them dimensions)
for (int i = 0; i < _summaries.size(); i++) {
ClassSummary sum = (ClassSummary)_summaries.get(i);
sum.layout(gfx);
}
// now arrange our summaries onto pages
_pages = new ArrayList();
ArrayList list = new ArrayList(_summaries);
ArrayList next = new ArrayList();
ElementLayout elay = new PackedColumnElementLayout();
while (list.size() > 0) {
// lay out the elements that fit on this page
Log.info("Laying out " + list.size() + " summaries in " +
width + "x" + height + "+" + x + "+" + y + ".");
elay.layout(list, width, height, next);
// remove the overflowed elements from the list for this page
list.removeAll(next);
// append this page to the pages list
_pages.add(list);
// move to the next page
list = next;
next = new ArrayList();
}
// finally adjust all of the bounds of the class summaries by the
// x and y offset of the page
for (int i = 0; i < _summaries.size(); i++) {
ClassSummary sum = (ClassSummary)_summaries.get(i);
Rectangle2D b = sum.getBounds();
sum.setBounds(b.getX()+x, b.getY()+y, b.getWidth(), b.getHeight());
}
}
/**
* Lays out and renders each of the classes that make up this package
* summary visualization.
*/
public int print (Graphics g, PageFormat pf, int pageIndex)
throws PrinterException
{
Graphics2D gfx = (Graphics2D)g;
// relay things out if the page format has changed or if we've
// never been laid out
if (!pf.equals(_format) || _pages == null) {
// keep this around
_format = pf;
// and do the layout
layout(gfx, pf.getImageableX(), pf.getImageableY(),
pf.getImageableWidth(), pf.getImageableHeight());
}
// adjust the stroke
gfx.setStroke(new BasicStroke(0.1f));
// make sure we're rendering a page that we have
if (pageIndex < 0 || pageIndex >= _pages.size()) {
return NO_SUCH_PAGE;
}
// render the summaries on the requested page
ArrayList list = (ArrayList)_pages.get(pageIndex);
for (int i = 0; i < list.size(); i++) {
((ClassSummary)list.get(i)).render(gfx);
}
return PAGE_EXISTS;
}
/**
* Renders the specified page to the supplied graphics context.
*/
public void paint (Graphics2D gfx, int pageIndex)
{
// ignore them if we haven't been laid out or if we have
// absolutely nothing to display
if (_pages == null || _pages.size() == 0) {
return;
}
// sanity check
if (pageIndex >= _pages.size()) {
Log.info("Requested to render non-existent page " +
"[index=" + pageIndex + "].");
return;
}
// adjust the stroke
gfx.setStroke(new BasicStroke(0.1f));
// render the summaries on the requested page
ArrayList list = (ArrayList)_pages.get(pageIndex);
for (int i = 0; i < list.size(); i++) {
((ClassSummary)list.get(i)).render(gfx);
}
}
/**
* Returns the number of pages occupied by this visualization. This is
* only valid after a call to {@link #layout}.
*
* @return the page count or -1 if we've not yet been laid out.
*/
public int getPageCount ()
{
return (_pages == null) ? -1 : _pages.size();
}
/**
* Cleans up a fully qualified class name according to our
* configuration and the package root.
*/
public String name (Class clazz)
{
if (clazz.isArray()) {
return name(clazz.getComponentType()) + "[]";
}
String name = clazz.getName();
if (_displayPackageNames) {
if (name.startsWith(_pkgroot)) {
return "." + name.substring(_pkgroot.length());
} else if (name.startsWith("java.lang")) {
return name.substring(10);
} else {
return name;
}
} else {
int ldidx = name.lastIndexOf(".");
return (ldidx == -1) ? name : name.substring(ldidx+1);
}
}
protected String _pkgroot;
protected ArrayList _summaries = new ArrayList();
protected ArrayList _pages;
protected PageFormat _format;
protected boolean _displayPackageNames = false;
protected static final int GAP = 72/4;
}
@@ -1,5 +1,5 @@
//
// $Id: LayoutUtil.java,v 1.1 2001/12/01 05:28:01 mdb Exp $
// $Id: LayoutUtil.java,v 1.2 2001/12/01 06:22:18 mdb Exp $
//
// viztool - a tool for visualizing collections of java classes
// Copyright (C) 2001 Michael Bayne
@@ -103,4 +103,38 @@ public class LayoutUtil
maxwid, bounds.getHeight() + height);
return bounds;
}
/**
* Returns a rectangle that accomodates the two specified columns of
* text at the bottom of the supplied rectangle, taking into account
* the preferred text spacing and the specified inset for the
* accomodated text.
*/
public static Rectangle2D accomodate (
Rectangle2D bounds, Font font, FontRenderContext frc, double inset,
String[] left, String[] right)
{
double maxleft = 0, maxwid = bounds.getWidth();
double height = 0;
Rectangle2D[] bndl = new Rectangle2D[left.length];
Rectangle2D[] bndr = new Rectangle2D[right.length];
// first compute our dimensions
for (int i = 0; i < left.length; i++) {
bndl[i] = new TextLayout(left[i], font, frc).getBounds();
bndr[i] = new TextLayout(right[i], font, frc).getBounds();
maxleft = Math.max(maxleft, bndl[i].getWidth());
}
// now that we have the maxleft width we can calculate the rest
for (int i = 0; i < left.length; i++) {
maxwid = Math.max(maxwid, maxleft+GAP+bndr[i].getWidth()+inset);
height += Math.max(bndl[i].getHeight(), bndr[i].getHeight());
}
bounds.setRect(bounds.getX(), bounds.getY(),
maxwid, bounds.getHeight() + height);
return bounds;
}
}
@@ -1,5 +1,5 @@
//
// $Id: RenderUtil.java,v 1.1 2001/12/01 05:28:01 mdb Exp $
// $Id: RenderUtil.java,v 1.2 2001/12/01 06:22:18 mdb Exp $
//
// viztool - a tool for visualizing collections of java classes
// Copyright (C) 2001 Michael Bayne
@@ -61,7 +61,7 @@ public class RenderUtil
Graphics2D gfx, FontRenderContext frc, Font font,
double x, double y, String[] text)
{
return renderStrings(gfx, frc, font, x, y, text, null);
return renderStrings(gfx, frc, font, x, y, text, (String)null);
}
/**
@@ -104,4 +104,57 @@ public class RenderUtil
// return the dimensions occupied by the rendered strings
return new Rectangle2D.Double(x, y, maxwid, y-starty);
}
/**
* Renders a two column array of strings to the specified graphics
* context, in the specified font at the specified coordinates. The
* left column is right align and the right, left aligned.
*
* @return the bounds occupied by the rendered strings.
*/
public static Rectangle2D renderStrings (
Graphics2D gfx, FontRenderContext frc, Font font,
double x, double y, String[] left, String[] right)
{
double maxleft = 0, maxwid = 0, starty = y;
double inset = 0;
// first generate text layout instances and compute bounds for all
// entries in both columns
TextLayout[] llay = new TextLayout[left.length];
Rectangle2D[] lbnds = new Rectangle2D[left.length];
TextLayout[] rlay = new TextLayout[right.length];
Rectangle2D[] rbnds = new Rectangle2D[right.length];
// compute the dimensions
for (int i = 0; i < left.length; i++) {
llay[i] = new TextLayout(left[i], font, frc);
lbnds[i] = llay[i].getBounds();
rlay[i] = new TextLayout(right[i], font, frc);
rbnds[i] = rlay[i].getBounds();
maxleft = Math.max(maxleft, lbnds[i].getWidth());
}
// do the rendering
for (int i = 0; i < left.length; i++) {
double lw = lbnds[i].getWidth();
llay[i].draw(gfx, (float)(x - lbnds[i].getX() + maxleft - lw),
// we actually mean to use rbnds[i] here because
// the right hand side (usually being the method
// declaration), tends to be taller than the left
// hand side (because of the parenthesis) and
// would appear a bit lower than the left hand
// side if we didn't use it's y offset
(float)(y - rbnds[i].getY()));
rlay[i].draw(gfx, (float)(x - rbnds[i].getX() + maxleft +
LayoutUtil.GAP),
(float)(y - rbnds[i].getY()));
maxwid = Math.max(maxwid, maxleft + LayoutUtil.GAP +
rbnds[i].getWidth());
y += Math.max(lbnds[i].getHeight(), rbnds[i].getHeight());
}
// return the dimensions occupied by the rendered strings
return new Rectangle2D.Double(x, y, maxwid, y-starty);
}
}