Copyright © 2004 Stoney Jackson, <hjackson@wnec.edu>
Distributed under the LGPL
 
 
The Peephole Framework is an application programming interface (API) for building pretty printers and text-based visualizations that use our peephole pretty printing algorithm. This document is an overview of the major components in the framework and how they can be used to create pretty printers and visualizations.
 
1.    Using the framework
 
To use the framework, you’ll need to place edu.ucdavis.pp_1.2.0.jar in your classpath. The source code is available in edu.ucdavis.pp_1.2.0-src.jar. To examine the source, extract this file using the jar utility that ships with Java SDK.
 
  1. pp: This and its subpackages contain the core of the peephole pretty printing API.
  2. toolbox: This and its subpackages provide some basic data structures and testing tools for our peephole pretty printer.
  3. spf: This and its subpackages contain the core for the Source Projection Framework (i.e., visualization API).
  4. edu.ucdavis.pp: This and its subpackages contain the peephole plug-in for Eclipse. You may want to refer to this for an example of how to use the peephole pretty printing framework.
  5. org.eclipse.jdt: This and its subpackages are also part of our peephole plug-in, but these classes work much more closely with JDT classes than do those in edu.ucdavis.pp.
  6. jjtraveler: This and its subpackages is a library for tree traversals and transformations. It is an external library that is maintained as part of the XT transformation tools, and is distributed under the GPL. Our pretty print algorithm relies on this library to perform tree traversals over its layout structures (which are trees).
 
3.     The framework’s components
A complete peephole visualization tool performs several tasks using several components to format a peephole of code (see Figure 1). The basic components are as follows:
 
  1. pp.PeepholePrettyPrinter: Provides pretty printing facilities for formatting peepholes from Lots.
  2. pp.lot.Lot: A Lot encodes the possible layouts for a particular document or part of a document.
  3. pp.lot.LotFactory: Factory for constructing Lots.
  4. pp.layout.LayoutElement: A LayoutElement is the smallest displayable unit that can be rendered by a Renderer.
  5. pp.layout.LayoutFactory: Factory for constructing LayoutElements from Lots.
  6. pp.layout.Renderer: Renders LayoutElements to a display.
 
The use of these components by an application to pretty print peepholes, shown in Figure 1, is as follows. First, the application creates a layout options tree (Lot) from some source code using a LotFactory. Next, it determines a focal node in the Lot and the width of the target peephole, and gives this information to a PeepholePrettyPrinter. Then, the application formats the peephole by calling the print methods on the PeepholePrettyPrinter. The PeepholePrettyPrinter uses a LayoutFactory and a Renderer to format the Lot. The LayoutFactory is responsible for constructing LayoutElements from Lot nodes, and for supplying width measurements of Lot nodes. The Renderer is used by the PeepholePrettyPrinter render the LayoutElements to a display environment. Each of these steps and their related components are described in greater detail below.
 
To create a Lot, an application instantiates a LayoutFactory and uses it to construct a Lot from source code. A Lot defines the possible layouts for a document. Thus, when an application creates a Lot for a document, it is specifying its format.
 
Getting an instance of a LotFactory is very simple:
 
LotFactory f = new LotFactory();
 
Once an application has a LotFactory, it can begin to construct Lots. Every Lot must be constructed from a LotFactory, and every Lot in the same Lot tree must be constructed from the same LotFactory. Lot nodes have a back-reference to the LotFactory that created them. This is needed at times to dynamically create other Lot nodes during formatting.
 
A LotFactory has several primitive methods for constructing a Lot. They are:
 
  1. Lot nil(): creates a Lot node representing nothing. Useful as a placeholder.
  2. Lot line(): creates a Lot node representing a new line that can be flattened into a space.
  3. Lot bline(): creates a Lot node representing a new line that can be flattened into nil Lots.
  4. Lot hline(): creates a Lot node representing a new line that cannot be flattened.
  5. Lot text(String s): creates a Lot node representing the string s. s must not contain new line characters. Use line(), bline(), or hline() to introduce new lines.
  6. Lot nest(int k, Lot lot): creates a Lot that indents each line in lot by k spaces.
  7. Lot cat(Lot left, Lot right): creates a Lot node that is the concatenation of left and right Lots.
  8. Lot choice(Lot left, Lot right): creates a Lot that has a choice between two Lots. The first line of text produced by the left must be at least as wide as that of the right.
 
Using these methods, one can express any format that is expressible in our framework. The other Lot constructing methods exist for convenience; all except for link(Extension e), which exists to preserve space efficiency through lazy construction of Lot nodes. Some of the other Lot constructing methods of note are:
 
  1. Lot link(Extension e): creates a lazy, dynamically expanding Lot that generates Lot nodes according to e.
  2. Lot flatten(Lot lot): creates a Lot identical to lot, but with each line replaced by a space and each bline replaced by an nil Lot. Flatten is implemented using lazy evaluation and link.
  3. Lot group(Lot lot): creates a choice between a flattened version of lot and lot itself [i.e., group(lot) = choice(flatten(lot), lot)]. Essentially, this creates an all-or-nothing policy for a group of lines and blines.
  4. Lot fill(List lots): creates Lot that places as many Lots in lots on each line (separated by spaces) that will fit before starting a new line. Essentially, this is a “paragraph fill”. Fill is implemented using lazy evaluation and link.
 
Each Lot node also contains a reference to an arbitrary “context” Object. This allows an application to associate an arbitrary Object with a Lot node, and gives the application the ability to retrieve that Object given a Lot node. For example, the Peepeye Browser associates with a Lot node the abstract syntax tree (AST) node that the Lot node represents. Later, a Lot’s AST node is retrieved to calculate a fisheye value.
 
To help an application associate context Objects with Lot node, a LotFactory provides an internal stack for tracking context Objects. The current context Object is inserted into each Lot it builds. The current context Object is set using the following methods:
 
  1. void pushContext(Object o): o is pushed onto the context stack and become the current context.
  2. Object popContext(): restores the current context to what it was before the last call to pushContext(o).
 
An application implementing a visualization technique that wishes to use the Peephole Framework’s Source Projection Facilities (SPF) should use an spf.SpfLotFactory instead of a the basic LotFactory. An SpfLotFactory is a LotFactory, except that context objects must be of type spf.vmap.SubjectValue. This will be discussed further in the section on Creating a visualization technique.
 
Determining the focus and dimensions of a peephole is application specific. The Peepeye Browser let’s users select a focus from a javax.swing.JTree, and determines the dimensions of the peephole from a javax.swing.JScrollPane’s viewport. Getting the dimensions from a JScrollPane’s viewport is fairly straight forward (see Java API documentation for JScrollPane). Getting the focus from a JTree is a bit more complicated, and is described next.
 
The Peepeye Browser gets the AST for a file from an XML file produced by JavaML. Therefore internally, AST nodes are XML DOM nodes. The browser creates a Lot from the AST. While doing so, it remembers which Lot nodes where created from each AST node, so that the Lot can be recalled for a given AST node. Then, the browser adds the AST nodes to the JTree. When a user selects a focus from the JTree, the browser extracts the AST node, looks up the corresponding Lot node.
 
6.Creating and using a PeepholePrettyPrinter
The PeepholePrettyPrinter gives control over formatting peepholes. Formatting a peephole is a 3 step process:
 
1)   Construct a PeepholePrettyPrinter.
2)   Set the focus and width of the peephole.
3)   Call print and state inspection methods to format the peephole.
4)   Get the results from the Renderer inside the PeepholePrettyPrinter.
 
Lot focus;
double peepholeWidth;
double peepholeHeight;
 
// 1) Construct a PeepholePrettyPrinter
PeepholePrettyPrinter printer = new PeepholePrettyPrinter();
 
// 2) Set the focus and width of the peephole.
focus = …
peepholeWidth = …
printer.setFocus(focus);
printer.setWidth(peepholeWidth);
 
// 3) Call print and state inspection methods to format the peephole.
// Formats a peephole with the focus near the top.
peepholeHeight = …
double oldHeight;
double newHeight;
do {
   oldHeight = printer.getHeight();
   printer.printDown();
} while ( newHeight > oldHeight && newHeight < peepholeHeight );
 
// 4) Get results
String result = (String) printer.getRenderer().getResult();
System.our.println(result);
 
 
The PeepholePrettyPrinter uses a LayoutFactory to construct LayoutElements from Lots, and uses a Renderer to render LayoutElements. By default, it uses the vanilla LayoutFactory and StringRenderer. These can be changed using the setLayoutFactory and setStringRenderer methods. This is useful when you want to retarget your pretty printer / visualization tool to a new display environment.
 
printer = new PeepholePrettyPrinter();
printer.setFocus(focus);
printer.setWidth(peepholeWidth);
 
printer.setLayoutFactory(…);
printer.setRenderer(…);
 
// Format a peephole width the focus near the middle.
// Format top half.
do {
   oldHieght = printer.getHeight();
   printer.printUp();
   newHeight = printer.getHeight();
} while ( newHeight < oldHeight && newHeight < peepholeHeight / 2 );
 
// Format bottom half.
do {
   oldHeight = printer.getHeight();
   printer.printDown();
   newHeight = printer.getHeight();
} while ( newHeight < oldHeight && newHeight < peepholeHeight );
result = (String) printer.getRenderer().getResult();
System.our.println(result);
 
7.    Adapting the PeepholePrettyPrinter to a display environment
By default the PeepholePrettyPrinter formats a Lot into a plain String. This is controlled by a LayoutFactory and a Renderer. A LayoutFactory provides measurements about Lot nodes to the PrettyPrintAlgorithm during pretty printing. These measurements depend on the medium being rendered to. Similarly, a Renderer is responsible for rendering LayoutElements to a display medium. To adapt a PeepholePrettyPrinter to a display environment, a custom LayoutFactory and Renderer must be created.
 
To adapt a LayoutFactory, extend LayoutFactory and override the getHieght, getWidth, and isElided methods. You can also override the factory methods, which begin with “to”, to construct custom LayoutElements. To create a custom Renderer for a display environment, extend Renderer and implement all abstract methods.
 
Often you’ll want to adapt a PeepholePrettyPrinter to a display environment that supports common textual attributes (e.g., font, font size, bold, italic, underline, colors, etc.). To simplify the management of attributes, and to allow construction of useful visualizations that use these attributes, the Peephole Framework provides a Source Projection Facility (SPF). Adaptation to these types of environments is described in Creating a visualization technique.
 
Also included in the Peephole Framework is an adaptation to Java’s Swing environment. This adaptation is based uses SPF as well, thus you can construct source code visualizations. To use this adaptation,
 
1)   Instantiate and initialize an spf.view.swing.AttributeMappingContext.
2)   Instantiate an spf.view.swing.SwingOracle.
3)   Instantiate a javax.swing.StyledDocument, which will receive formatted text.
4)   Instantiate an spf.view.swing.SwingKit.
5)   Instantiate, initialize, and use a pp.PeepholePrettyPrinter to format peepholes.
 
// 1) Instantiate and initialize an AttributeMappingContext.
AttributeMappingContext mapping = new AttributeMappingContext();
… // initialization shown in Creating a visualization technique.
 
// 2) Instantiate a SwingOracle.
SwingOracle oracle = new SwingOracle(mapping);
 
// 3) Instantiate a StyledDocument.
StyledDocument document = new DefaultStyledDocument();
 
// 4) Instantiate a SwingKit.
SwingKit kit = new SwingKit(oracle, document) ;
 
// 5) Instantiate, initialize, and use a PeepholePrettyPrinter to format peepholes.
PeepholePrettyPrinter printer = new PeepholePrettyPrinter();
printer.setLayoutFactory(kit.getFactory());
printer.setRenderer(kit.getRenderer());
… // using a PeepholePrettyPrinter is described in Creating and using a PeepholePrettyPrinter.
 
The Swing adaptation is built on top of SPF. Besides making visualizations easy to create, SPF also simplifies adaptation to different display environments. To create an adaptation using SPF, implement the spf.SpfOracle interface which provides width, height, and elision information for spf.vmap.SubjectValues. A SubjectValue represents the context under which some text is being rendered. The SubjectValue is used by the SpfOracle to determine the visual attributes of the text. For example, in the Peepeye Browser, SubjectValues are wrapped DOM (an XML object-oriented representation) nodes that represent AST nodes. Thus, a text’s attributes are determined by which AST node they represent. This could be used, say, to underline all keywords.
 
  1. double getWidth(SubjectValue subject, String text): Returns the width of text under the context of subject.
  2. double getHeight(SubjectValue subject, String text): Returns the height of text under the context of subject.
  3. boolean isElided(SubjectValue subject, Lot lot): Returns true if lot should be elided under the context of subject.
 
For a detailed example of adapting to a display environment using SPF, see the source code in the spf.view.swing package.
8.    Creating a visualization technique
The basic idea behind a source code visualization is to encode some information in the visual attributes of the code (e.g., font, font size, color, etc.). However, changes in font, size, weight (i.e., “boldness”), or elision of text can adversely affect the layout of code. To maintain the integrity of a code’s layout, the PeepholePrettyPrinter needs to know the width of each LayoutElement while it is formatting. This information is given to the PeepholePrettyPrinter through a LayoutFactory and manifested by a Renderer. So, by implementing a custom LayoutFactory and Renderer, source code visualizations can be implemented.
 
The Peephole Framework also includes a Source Projection Facility (SPF) that simplifies the implementation of source code visualizations (currently only for Swing). In SPF, you construct maps from SubjectValues to AttributeValues. A SubjectValue represents the context under which some text is being rendered. An AttributeValue belongs to an AttributeType that govern its legitimate values. SPF provides 13 different AttributeTypes. Examples of these include FamilyType, SizeType, BoldType, ItalicType, and UnderlineType.
 
Mappings are constructed in two stages. The first is a NormedSource. A NormedSource takes a SubjectValue and produces a real value between 0 and 1 inclusively: a Z1Value. The second stage is an Encoder. An Encoder takes a Z1Value and produces an AttributeValue. The reason for the two stages is so that the NormedSources and Encoders can be mixed and matched and reused.
 
The Peepeye Browser creates a furnas.FisheyeNormedSource that takes a SubjectValue representing an AST node and calculates its fisheye value (a fisheye value is a measure of an AST node’s distance from the path between a focal node and the root; see Furnas CHI’86). Then it normalizes that fisheye value to a Z1Value by dividing by the largest possible fisheye value.
 
The browser then uses a furnas.FisheyeElisionEncoder and creates a peepeye.FishseyeFontSizeEncoder to map Z1Values to an ElisionType value (a boolean value with true meaning to elide) and a SizeType. Specifically, the FisheyeElisionEncoder maps 1 to mean elide, and all other values to mean show. The FisheyeFontSizeEncoder simply interpolates a font size between a minimum and maximum font size with 0 being the minimum and 1 being the maximum. Here is an excerpt from peepeye.PeepholeView demonstrating the construction and association of sources and encoders.
 
fisheyeNormedSource = new FisheyeNormedSource(fisheyeParameters.getDegree());
 
elisionMapping = new AttributeMap(fisheyeNormedSource, new FisheyeElisionEncoder());
 
int minFontSize = fisheyeParameters.getMinimumFontSize();
int maxFontSize = fisheyeParameters.getMaximumFontSize();
fontSizeEncoder = new FisheyeFontSizeEncoder(minFontSize, maxFontSize);
 
fontSizeMapping = new AttributeMap(fisheyeNormedSource, fontSizeEncoder);
 
Now we need to tell the pretty printer to use these mappings. This is done by adding these mappings to the spf.view.swing.AttributeMappingContext associated with an spf.view.swing.SwingOracle. We also need to set default AttributeValues for AttributeTypes we aren’t using (it doesn’t hurt to set them for those that we are; in fact it’s recommended that you do).
 
AttributeMappingContext mappings = new AttributeMappingContext();
SwingOracle oracle = new SwingOracle(mappings);
mappings.setMapping(elisionMapping);
mappings.setMapping(fontSizeMapping);
attributeMappingContext = new AttributeMappingContext();
attributeMappingContext.setDefault(new SizeValue(12));
attributeMappingContext.setDefault(new FamilyValue("Courier"));
attributeMappingContext.setDefault(new ElisionValue(false));
attributeMappingContext.setDefault(new BoldValue(false));
attributeMappingContext.setDefault(new ItalicValue(false));
attributeMappingContext.setDefault(new UnderlineValue(false));
 
Finally we construct the other Swing adaptation components and pretty printer as described in Adapting the PeepholePrettyPrinter to a display environment.