The exercises work with a figure editor together with JUnit test cases. They progress, as most users do in their adoption of AspectJ, from non-functional, development-only aspects to aspects which augment a deployed program with crosscutting features.
For each exercise, you will compile and run with the AspectJ
runtime and the JUnit test cases. The JUnit tests verify your code by
inspecting messages you send using the Log
class which is
in the support
directory.
If you are using Eclipse, compiling your project is accomplished
with the compile button to the left of the perspective pull-down menu
in the toolbar. If you are using the command-line tools, then you
should compile all the files in base.lst, as well as whatever aspect
code you add for an exercise (figures/Answer1a.java
, in
this example):
$ ajc -Xlint -argfile base.lst figures/Answer1a.java
If you are using Eclipse, running one of the unit tests is
accomplished by right clicking the test (in the tests
package) and selecting "Run". If you are using the command-line
tools, then just invoke java on the compiled test class, such as
$ java tests.Test
The default test, tests.Test
, performs some
rudimentary tests on figure elements, and so is a useful test to
run periodically.
To complete each exercise, create a file containing your aspects
and include the file in your compile (e.g., replacing
figures/Answer1a.java above). Use the AspectJ documentation as a
reference, particularly the quick-reference and semantics appendices
of the programming guide. The compiler's -Xlint
option
may catch some common mistakes. If you get stuck, inspect the test
case included for each exercise.
Intstrument your code so that before the Point's setX method
begins executing, you log the string "set". To do this, use the
utility class Log
(with an import from
support.Log
) and call
Log.log("set")
This will write the string "set", followed by a semicolon terminator, to the Log. For example, with your aspect enabled,
Point p1 = new Point(10, 100); p1.setX(37); System.out.println(Log.getString());
should print out "set;".
Test this with the JUnit test case tests.Test1a
.
Without adding any aspects, this test should fail:
$ ajc -argfile base.lst $ java tests.Test1a ..F....... Time: 0.07 There was 1 failure: 1) testSetXPointLog(tests.Test1a)junit.framework.AssertionFailedError: expected:<set;> but was:<> at tests.Test1a.testSetXPointLog(Test1a.java:30) at tests.Test1a.main(Test1a.java:16) FAILURES!!! Tests run: 9, Failures: 1, Errors: 0
But with the proper aspect added to the compilation, (in this
case, figures/Answer1a.java
, but you should feel free to use
more evocative names), the test should pass
$ ajc -argfile base.lst figures/Answer1a.java $ java tests.Test1a ......... Time: 0.089 OK (9 tests)
Make sure to pass tests.Test1a
before
continuing.
Continue your aspect to capture execution of more methods.
When Point.setX
or Point.setY
execute,
you should log "set". When one of
Point.getX, Point.getY, Line.getP1, Line.getP2
execute, you should log "get".
Feel free to use wildcards in your pointcuts.
For example,
Point p1 = new Point(10, 100); p1.setY(p1.getX()); System.out.println(Log.getString());
should print out "get;set;".
Make sure to pass the JUnit test tests.Test1b
before continuing.
Instead of logging "set"
and "get"
, log
the name of the executing method. For example,
Point p1 = new Point(10, 100); p1.setY(p1.getX()); System.out.println(Log.getString());
would print out "getX;setY;".
Make sure to pass the JUnit test tests.Test1c
before continuing.
At this point, check the people to your left and right. If they're stuck somewhere, see if you can help them. If the people to your left and right are doing fine, then think about refactoring your logging aspect from part c. You could separate your logging aspect into two aspects, one encapsulating the logging protocol, and the other encapsulating the choice of join points. See if there are more interesting refactorings to do.
In your refactoring, you make sure you continue to pass the JUnit
test tests.Test1c
.
If you did some refactoring in the last exercise, restore your
packages to their original contents (which were saved in the
backup
directory wherever you unpacked
figures.zip
) before starting the next exercises.
It can often be useful to trace only those calls that are made to
outside code. Write an aspect to log calls to methods in the
java.util
package from the figures
package.
You should send a short description of the join point to the log, with
Log.log(thisJoinPointStaticPart.getSignature().toShortString());
For example, if you compiled your aspect with this
Main
class
package figures; class Main { public static void Main(String[] args) { java.util.Set s = new java.util.HashSet(); s.add(null); s.clear(); System.out.println(Log.getString()); } }
running it would print out "Set.add(Object);Set.clear();".
Make sure to pass the JUnit test tests.Test2a
before continuing.
Instead of logging whenever we make an external call to method
from java.util
, modify your code to log any
Throwable
thrown from such a call from code in the
figures
and test
packages.
For example, if you compiled your aspect with this
Main
class
package figures; class Main { public static void Main(String[] args) { java.util.Vector v = new java.util.Vector(); try { v.get(1); } catch (ArrayIndexOutOfBoundsException e) { } System.out.println(Log.getString()); } }
running it would print out "caught
java.lang.ArrayIndexOutOfBoundsException;"
.
Make sure to pass the JUnit test tests.Test2b
before continuing.
As an extremely artifical example leading up to the next exercise, write an aspect that writes to the log:
Log the size of the calling group and the p0 point of the target box.
For example, if you compiled your aspect with this
Main
class
package figures; class Main { public static void Main(String[] args) { Group g = new Group(new Point(30, 70)); Box b = new Box(5, 5, 10, 10); g.add(b); g.move(100, 100); // logs "2Point(5, 5)" g.add(new Point(1, 99)); g.move(-2, -10); // logs "3Point(105, 105)" System.out.println(Log.getString()); } }
running it would print out
2Point(5, 5);3Point(105,105);
As you may already have noticed, looking at the included JUnit tests will also help with the problems.
Make sure to pass the JUnit test tests.Test2c
before continuing.
Write an aspect that logs when the _x
and
_y
fields on Point
are set, but only when in
the process of moving a Box
.
Make sure to pass the JUnit test tests.Test2d
before continuing.
Write an aspect that allows the specification of individual figure
elements whose move method should be logged. This will involve
associating a boolean value with every figure element. Use around
advice to intercept calls to Log.traceObject(Object)
(which otherwise just signals an exception) to register the passed
object as traceable. Then, write advice that logs the class name of
any such moved object, but only if the object is registered as being
traced. You may wish to use the if
pointcut.
The Log class has a method that will log the short name of a Class
minus its package name. Since this is the form expected by the test
case, you should probably use support.Log.logClassName(Class)
.
Make sure to pass the JUnit test tests.Test2e
before continuing.
At this point, check the people to your left and right. If they're stuck somewhere, see if you can help them. If the people to your left and right are doing fine, then think about refactoring any of the aspects or the system in general, perhaps starting by merging part d and e.
In your refactoring, make sure you continue to pass appropriate the JUnit tests.
If you did some refactoring in the last exercise, restore your packages to their original contents (which you should be able to do by copying the contents of backup) before starting the next exercises.
One common coding convention is that no private field should be set outside of setter methods or constructors. Write an aspect to warn at compile time when such an illegal assignment expression exists.
You will find that the convention is violated twice in the figures package, but you may or may not agree that the "violation" is bad style. Deal with this information, either by making your aspect more nuanced by relaxing the coding convention, or by fixing the code to remove the violation.
Make sure your program still passes the JUnit test
tests.Test
(which it should also pass at the beginning of
all exercises) before continuing.
Write an aspect to throw an IllegalArgumentException
whenever an attempt is made to set one of Point
's
int
fields to a value that is either too large (greater
than FigureElement.MAX_VALUE
) or too small (less than
FigureElement.MIN_VALUE
).
Make sure to pass the JUnit test tests.Test3b
before continuing.
Instead of throwing an exception when one of Point
's
int
fields are set to an out-of-bounds value, write an
aspect to trim the value into an in-bounds one.
Make sure to pass the JUnit test tests.Test3c
before continuing.
There is a method on the Box
class, void
checkBoxness()
, that checks whether the four points making up a
box are correctly positioned relative to each other. Write an aspect
that will make sure that after all calls to public methods of Box
return, the checkBoxness()
method is called.
When testing this aspect, you may find yourself facing a
StackOverflowException
. If so, carefully look at your
pointcuts.
You may also see an irrecoverable stack overflow occurring, which may either show as such or simply abort silently, depending on your JVM. In this case, you should also check carefully to see what you're doing wrong, but here's a hint: The following program probably has the same behavior:
class Test { public static void main(String[] args) { try { main(args); } finally { main(args); } } }
Make sure to pass the JUnit test tests.Test3d
before continuing.
FigureElement
s support the getBounds()
method that returns a java.awt.Rectangle
representing the
bounds of the object. An important postcondition of the
move
operation is that the figure element's bounds
rectangle should move by the same amount as the figure itself. Write
an aspect to check for this postcondition.
You can create a copy of a figure element's bounds rectangle with
new Rectangle(fe.getBounds())
, and you can move a bounds
rectangle rect
with rect.translate(dx, dy)
.
Make sure to pass the JUnit test tests.Test2f
before continuing.
At this point, check the people to your left and right. If they're stuck somewhere, see if you can help them. If the people to your left and right are doing fine, then think about refactoring any of the aspects or the system in general.
In your refactoring, make sure you continue to pass appropriate the JUnit tests.
If you did some refactoring in the last exercise, restore your
packages to their original contents (which were saved in the
backup
directory wherever you unpacked
figures.zip
) before starting the next exercises.
Figure elements currently have a hard-coded color. The first part
of crosscutting functionality you will add will be support for
figureElements to have settable line and fill colors. We have
provided a stub aspect figures.ColorControl
for you to
fill in; that aspect's static methods are called from the JUnit test
tests.Test4a
.
Make sure to pass the JUnit test tests.Test4a
before continuing.
The second crosscutting feature you will be adding is support from
caching the bound objects of Group
figure elements, which
may be costly to compute. Before starting that, write a tracing
aspect to log all of a point's enclosing group identifiers whenever
that point moves.
You need only deal with points that are added directly to Groups. You should handle those points contained in Lines and Boxes only if time permits.
Make sure to pass the JUnit test tests.Test4b
before continuing.
Now that you have a structure in place to maintain information
about a point's enclosing groups, cache group's bounds rectangle so
that repeated calls to getBounds()
returns the same
rectangle. However, whenever a point moves it should invalidate the
caches of all enclosing groups.
Make sure to pass the JUnit test tests.Test4b
before continuing.
Congratulations! As before, check the people to your left and right and see if they're stuck. Otherwise, do any other refactorings that would make the code more robust or easier to maintain. Consider whether abstracting out parts of the gui-handling code would improve modularity. Bring back some of your tracing aspects from exercise 1 and explore the newly modularized system.