DJ User Manual

The DJ home page DJ Home Page (http://www.ccs.neu.edu/research/demeter/DJ/) gives the interface to DJ and describes the most important behavior. Here is additional information that will be integrated into the home page.

DJ is a Java library that simplifies navigation through object structures. DJ is very light-weight and easy to use. It is possible to take any program that uses the DJ library and rewrite it by explicitly programming the navigation. To get rid of DJ, you need to write your own abstract Visitor class and to implement the DJ functions manually. This can be a very tedious task that makes your program brittle, of course, but it shows that DJ is close to Java.

A few design patterns that help you understand DJ

An example demonstrating ease of maintenance of Java programs written with DJ.

A simple example

The simplest use of DJ is to use it to access data members. Why is a method like
class A {
  B get_b() { return b;} 
}
not good enough? The issue is stability and robustness. What if A becomes more complex? We would have to write something like:
class A {
  B get_b() { return get_z().get_y().get_x().get_b();} 
}
which is hard to maintain under often changing class structures. The intent is to go from A to B and in DJ we can express this directly:
class A {
  B get_b() {return (B) Main.cg.fetch(this, "from A to B");}
}
We assume that there is exactly one B-object contained in an A-object but we don't care how "far away" it is. Main.cg refers to a class graph object that you best think of as the UML class diagram behind your application.

DJ and the use of traversal strategies

Here is some advice on how to use traversal strategies with DJ. This advice is needed because Java does not support yet parameterized classes. Traversal strategies that operate on class graphs that contain unconstrained collections introduce more paths than you might imagine. A general description of this situation is: If the strategy is A -> B and one path selected by this strategy contains an untyped collection with target B and elsewhere in the class graph there is an edge "surprise" leading to B then "surprise" will be in the traversal graph. Notice that "surprise" is in the traversal graph although there is no path from A to the source of "surprise" if the untyped collection is replaced by a typed collection.

The additional edges you get in the traversal graphs have two negative consequences: the traversal graphs become unnecessarily big and if the collection objects contain "accidental" objects they may be traversed accidentally. It is important to stress that the traversals will be correct if only legal objects are used.

This means that untyped collections make it unnecessarily difficult to use traversal strategies. You have the following options:

  1. Use untyped Java collections but make sure that you have only objects allowed by the class graph in the collection objects. You could check for this at run-time. This gives you correct traversals for correct objects.
  2. Use untyped Java collections and to control the size of the traversal graph use enough bypassing clauses in your traversal strategies. This gives you correct traversals and small traversal graphs. But it makes the traversal strategies harder to maintain.
  3. Use arrays that are typed in Java. This has the disadvantage that at run-time the traversal will always go through all array elements. This is only suitable in some situations.
  4. Use a list structure that is type safe, like an abstract class PersonList with two subclasses EmptyPersonList and NonEmptyPersonList. This can be used in connection with code generators.
The DJ lecture notes, files of the form *DJ*.* contain an example (see DJ.*) and more information.

There is an interesting interplay between constrained genericity in class graphs and positive strategies. A positive strategy does not use "bypassing". Both constrained genericity for class graphs and positive strategies need to be present for a convenient programming system.

If we only have unconstrained genericity as with Java collections the traversal graphs get too big and unnecessary traversal is performed at run-time if the collection objects contain illegal eleemts. With constrained genericity as with C++ templates or Demeter parameterized class graphs we have more information at traversal graph computation time and the traversals become more efficient.

If we don't have positive strategies, i.e. all strategies are of the form

from {A,...} bypassing{...} to {B,...}
we cannot express positive traversal intent outlining "where we want to go" at a high level.

As long as Java lacks constrained genericity, the best solution is to decorate the positive strategies with sufficient bypassing clauses for edges to make the traversals efficient. But this makes the traversal strategies harder to maintain. However, this is only a temporary work-around until Java gets GJ or a version of it.

DJ allows you to inspect the traversal graphs.
An example of a traversal graph is:

Traversal Graph for "from A via B to C"
X: in copies {0, 1}
C: in copies {1}
B: in copies {0, 1}
A: in copies {0}
-> B,x,X: in copies {0, 1}
-> A,x,X: in copies {0}
-> X,b,B: in copies {0, 1}, intercopy table {{0, 1}}
-> X,c,C: in copies {1}         

The class graph is:
// class graph
A = [X].
B = [X].
C = [String].
X = [B] [C]. 

The traversal graph should be read as follows:
All nodes and all edges are in the traversal graph.
For the traversal "from A to B" the nodes and edges labeled 0
are in the traversal graph.
For the traversal "from B to C" the nodes and edges labeled 1
are in the traversal graph.
The edge labeled "intercopy table {{0, 1}}" goes from copy one to
copy two.

The details of the traversal graph computation are in the strategy
graph paper.

A convenient way of programming with DJ is to first construct your class graph:
ClassGraph cg = new ClassGraph(); // constructed from *.java

You may want to make the class graph static so that it is available
globally:

class Main {
    static ClassGraph classGraph ...
Later you can refer, even in other classes, to the classGraph
with Main.classGraph.

You define the traversal graphs you need for your computations
by first defining the strategy for each traversal graph
followed by the strategy graph computation:

Strategy sg = new Strategy("from A to D");
TraversalGraph tg = TraversalGraph.compute(cg, sg);

The traversal graphs are then used in calls to functions. An example
is function count:

public int count(TraversalGraph tg){
  BCounter bc =  new BCounter();
  tg.traverse(this, bc);
  return bc.count;
}                                 

============
Programming with simple class graph views in DJ:
In DJ you can define views of your class graph by using the
constructur of ClassGraph with a string. For example:

 static ClassGraph classGraphView = new ClassGraph(
     "class A {public B b;} class B {public D d;} class D {}" );

This allows you to define a view of your class graph that 
eliminates edges that are not needed for the current task.
Such a class graph view might simplify the writing of
many strategies because bypassing clauses will be less needed.

The class graph view can only delete edges and nodes that will
never be traversed. It is important that the traversal graph
for the view
TraversalGraph tg2 = TraversalGraph.compute(classGraphView, sg);
defines a legal traversal in the original class graph.
See /course/com3362/sp99/DJ/participant-graph
for an example.

DJ makes it also easy to overlay your concrete class graph
with a participant class graph that uses the same
class names. You introduce getters and setters using traversal
strategies. Currently you can have only one layer 
of participant graphs
on top of the concrete class graph.
See /course/com3362/sp99/DJ/participant-graph
for an example.