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.
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.
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:
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.