Subject: Final version of the Object Form Checker...
From: Paul Freeman (pfreeman@ccs.neu.edu)
Date: Fri Nov 08 2002 - 21:01:56 EST
Hi Karl, Class -
Here is the final version of the object form checker. I made a few
modifications to improve its design, one of them being the addition of a
AnyDynamic abstract class that contains all of the pointcuts to be
modified by the user to vary the functionality of the checker.
Everything is described in the enclosed solution description addendum.
At the end is a brief list of remaining known issues. If any more are
found, please let me know. This version also has been redesigned to
facilitate unit testing of the individual bin objects as well as the
checker. Enclosed with the solution are a series of ".lst" files which
MUST be modified (to contain the correct paths to the files) but can
used to compile the portions of the program separately. Calling "java
lawOfDemeter.Tests" will run a small series of tests, sending output to
the log file. To use the logging features, you will need to read the
end of the part2 solution. Otherwise the program can be compiled
without the loggin aspect or you could replace the logging aspect with
one of your own.
I would suggest those of you in class use this version in you plug-in.
Paul
************************************************************************************
Addendum:
************************************************************************************
The design for Part 3 was modified to improve the separation of concerns.
The improved design allows for Unit testing of the various portions of
the LoDChecker
application. Changes can be noticed by comparing the code for Part 2
with the code
for Part 3.
What was done:
A small modification was made to some of the pointcuts in the Any
class. A ToCheck
pointcut was added so that it could be used in some of the other changes
mentioned
below:
// denotes all the join points to check in the code being examined
pointcut ToCheck(): AnyDynamic.ToCheck;
Also, the dynamic pointcuts, i.e. pointcuts to be changed by the user
have now been moved to their own class called AnyDynamic. This facilitates
quick identification of the pointcuts that can be modified by the user, and
it makes it quite simple to rewrite this simple class prior to a
build based on options selected by a user through a user interface.
Major Changes:
There was an undesirable dependency between Checker and all of the
implimentations
of Supplier, namely that the code in Checker was hardwired with the
various Supplier
implimentations that were being used. It would be ideal if the checker
aspect
was only aware of the supplier class itself, and did not care what
implimentations
of supplier exist. It would also be ideal if future implimentations of
Supplier
could also be added without modifying any existing code in the program. The
solution provided accomplishes just this.
First, an abstract method was added to Supplier:
abstract public Supplier getSupplier(Object thisObj);
Each aspect that impliments Supplier must provide an implimentation of
the get
supplier method which returns a Supplier based on the thisObj supplied
if necessary.
Then a static Vector was added to Supplier to hold all of the
implimentations of
Supplier being used in the program. This vector is populated through a
small bit
of advice:
/**
* Advice that gathers supplier objects at a join point before that
* join point is checked in the checker aspect.
*/
before():
Any.ToCheck(){
Supplier s = getSupplier(thisJoinPoint.getThis());
if(s != null){
suppliers.add(s);
}
}
This advice will execute once for every aspect that impliments supplier,
adding
that instance of Supplier to the vector of suppliers before the advice
in the
Checker aspect is executed. The Supplier aspect also needed to be
modified so
that it declared that it "dominates Checker" to ensure the advice above
executed before
the advice in Checker. Notice that there is no code here to erase the
vector of
suppliers prior to adding an instance of Supplier in the advice, i.e.
before():
Any.ToCheck(){
suppliers = new Vector(); //***** reset suppliers
Supplier s = getSupplier(thisJoinPoint.getThis());
if(s != null){
suppliers.add(s);
}
}
Unfortunately a reset of the supplier vector cannot be placed here
because each
implimenting aspect would reinitialize the vector before it added itself
to the
vector, resulting in suppliers containing at most one Supplier instance
and possible
no Supplier instances.
To accomplish this correctly, what we really need is a piece of Static
advice
will execute only once, reinitializing the supplier vector prior to the
execution of the advice above. Since AspectJ does not allow advice to
be declared
as static, we need to create another small concrete aspect that provides
this
functionality:
privileged aspect ConcreteSupplier dominates Supplier {
// initialize the suppliers vector
before(): Any.ToCheck() {
Supplier.suppliers = new Vector();
}
}
Finally, as is good coding practice, a get method was added to Supplier
to return
the current Vector of available suppliers.
protected static Vector getSuppliers(){
return suppliers;
}
Some other small modifications were made to Supplier and
TargetBinStack. The
public String contains() method was moved up from TargetBinStack into
Supplier
and a printTargets() method was also added to supplier - this method
makes use
of the existing printHashMap method to printed a more nicely formatted
display.
TargetBinStack, now overrides the printTargets method and also provides
another
method called printAllTargets. All the printing methods are only used for
debugging purposes.
The other major modification that was made was the abstraction of the
statistic
gathering portion of the application out of Checker and into a separate
aspect
called StatMonitor. Checker now only does what you would expect, it checks.
However it was modified slightly to provide an event hook to any Aspects
that
wish to monitor the checking. Two methods were added along with two
pointcuts:
/**
* Prints a violation noitice using the thisJoinPoint argument.
* Also provides a method that can be captured using the included pointcut
* Checker.Violation().
*
* @param thisJoinPoint
*/
pointcut Violation(JoinPoint tjp):
execution(private void Checker.raiseViolation(JoinPoint)) && args(tjp);
private void raiseViolation(JoinPoint thisJoinPoint){
System.out.println("!! LoD violation:
"+thisJoinPoint+JPUtil.at(thisJoinPoint));
}
/**
* Prints a success noitice using the thisJoinPoint argument.
* Also provides a method that can be captured using the included pointcut
* Checker.Success().
*
* @param thisJoinPoint
* @param binsIn
*/
pointcut Success(JoinPoint tjp, Vector binsIn):
execution(private void Checker.raiseSuccess(JoinPoint, Vector)) &&
args(tjp, binsIn);
private void raiseSuccess(JoinPoint thisJoinPoint, Vector binsIn){
// used for debugging
// if(binsIn.size() > 1){
// System.out.println("Call OK
("+thisJoinPoint+")"+JPUtil.at(thisJoinPoint)+
// " target found in " + binsIn);
// }
}
The StatMonitor aspect now simply uses the pointcuts provided by checker to
analyze the statistics of the program. The new pieces of advice are
included
below, all the other statistic code that was in checker has been moved
unchanged
to the StatMonitor aspect and so will not be shown below:
public aspect StatMonitor {
// statistcs variables initialized before every main method execution
private long totalChecks;
private long numViolations;
private Vector validChecks;
// initialize statistical variables
before():
Any.MainMethodExecution(){
totalChecks = 0;
numViolations = 0;
validChecks = new Vector();
validChecks.add(new Long(0)); // set the total valid counts = 0
}
// increase the total count of checks
before():
Any.ToCheck(){
totalChecks++;
}
// increase the violation count
before(JoinPoint tjp):
Checker.Violation(tjp){
numViolations++;
}
// increase the correct count
before(JoinPoint tjp, Vector binsIn):
Checker.Success(tjp, binsIn) {
addToStats(binsIn.size());
}
...
}
A few get functions were also added to provide access to the current
counts during
program execution.
Bug Fixes:
A few bug fixes were also implimented. One major bug noticed by
Pengcheng Wu was
in the use of perthis(<join point>) in the AspectDeclaration of the
ImmediatePartBin. It
turns out that not only does AspectJ associate an aspect with the "this"
object
of the join point described in the perthis statement, all of the advice
in the
corresponding aspect instance only executes if and only if the advice
uses a join
point where the aspect is associated with the "this" object. This is
similar
for the pertarget aspect declaration keyword.
The difference can be seen through the following example. Suppose we have:
aspect A perthis(Any.ConstructorExecution()){
before(): Any.MethodCall(){
System.out.println("Method Call");
}
}
class Foo {
public void foo(){ System.out.println("foo");}
public static void main(String[] args){
Foo f = new Foo();
f.foo();
}
}
You might expect the output to be:
Method Call // call to main
Method Call // call of f.foo
foo
Method Call // call to println
However, there is no aspect A associated with the null "this" object at
the point
of the call to main, so method call will not be printed. There is also
no aspect A
associated with the null "this" object at the point f.foo() in the main
method.
This is because the currently executing objects in both cases are null,
i.e. this == null.
So your output will be only:
foo
Method Call // call to println
If you change the perthis to pertarget in A however, you will still not
get the first
method call to print as there is no aspect A associated with the null
target object
at the call to main. However, the "Method Call" statement will print at
the line
f.foo() since there is an instance of Aspect A associated with the
target object f
of type Foo at that join point.
So the complete output will be:
Method Call // call of f.foo
foo
Method Call // call to println
Another bug in the ImmediatePartBin was in the adding of the arguments
to the set
join point. Set join points may or may not occur in the object in which the
member variable exists. For example, assuming:
aspect A pertarget(Any.ConstructorExecution()){
before(): Any.MethodCall(){
System.out.println("Method Call");
}
}
class Foo {
Bar b;
public void foo(){ System.out.println("foo");}
public static void main(String[] args){
Foo f = new Foo();
f.foo();
f.b = new Bar();
}
}
class Bar{
}
The "this" object at the line f.b = new Bar() above is actually null
since it
occurs in a static method. However, the target object at that point is the
correct object, i.e. the Foo object whose immediate part b we would like to
capture the set of. So the actuall addition of the new Bar() object
should be
made to the target of the set, not necessarily the object associated
with the
Aspect. Although the previous reasoning about pertarget would make this
seem
unnecessary, it is better to be safe than sorry so the advice was
changed to:
before(): // direct parts
Any.Set() {
// There is only one argument to a set join point.
// Becomes a preferred supplier and is therefore added to targets.
ImmediatePartBin.aspectOf(thisJoinPoint.getTarget()).addAll(thisJoinPoint.getArgs(),
direct+at(thisJoinPoint));
}
A modification was also made to the add method in supplier, disallowing the
addition of any null objects to the targets hashmap.
And finally, a bug was fixed in the original statistic gathering code
when it
was moved to the StatMonitor aspect.
KNOWN ISSUES:
Objects are stored in the hashmap based on their toString() value, an
invalid target
with the same value as a valid target will not raise a violation
Old immediate part object values are never removed from the hashmap
after they
have been replaced with a new value.
There is no strong form implimentation in this Object form checker.
This archive was generated by hypermail 2b28 : Fri Nov 08 2002 - 21:07:57 EST