blob: 1c72dfccc34a68399a3790b97d0338dd1f9e31a8 [file]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html> <head>
<title>AspectJ Tutorial Exercises</title>
</head>
<body>
<h1>AspectJ Tutorial Exercises</h1>
<h3>Organization</h3>
<p> 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. </p>
<p> 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 <code>Log</code> class which is
in the <code>support</code> directory. </p>
<p> 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 (<code>figures/Answer1a.java</code>, in
this example): </p>
<blockquote><PRE>
$ ajc -Xlint -argfile base.lst figures/Answer1a.java
</PRE> </blockquote>
<p> If you are using Eclipse, running one of the unit tests is
accomplished by right clicking the test (in the <code>tests</code>
package) and selecting "Run". If you are using the command-line
tools, then just invoke java on the compiled test class, such as </p>
<blockquote><PRE>
$ java tests.Test
</PRE> </blockquote>
<p> The default test, <code>tests.Test</code>, performs some
rudimentary tests on figure elements, and so is a useful test to
run periodically.
</p>
<p> 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 <code>-Xlint</code> option
may catch some common mistakes. If you get stuck, inspect the test
case included for each exercise. </p>
<hr>
<!-- page break -->
<h2>1. Log simple properties</h2>
<h3>a. Trace one method</h3>
<p> 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 <code>Log</code> (with an import from
<code>support.Log</code>) and call </p>
<blockquote><PRE>
Log.log("set")
</PRE></blockquote>
<p> This will write the string "set", followed by a semicolon
terminator, to the Log. For example, with your aspect enabled,
</p>
<blockquote><PRE>
Point p1 = new Point(10, 100);
p1.setX(37);
System.out.println(Log.getString());
</PRE></blockquote>
<p> should print out "set;".
</p>
<p> Test this with the JUnit test case <code>tests.Test1a</code>.
Without adding any aspects, this test should fail: </p>
<blockquote><PRE>
$ ajc -argfile base.lst
$ java tests.Test1a
..F.......
Time: 0.07
There was 1 failure:
1) testSetXPointLog(tests.Test1a)junit.framework.AssertionFailedError: expected:&lt;set;&gt; but was:&lt;&gt;
at tests.Test1a.testSetXPointLog(Test1a.java:30)
at tests.Test1a.main(Test1a.java:16)
FAILURES!!!
Tests run: 9, Failures: 1, Errors: 0
</PRE></blockquote>
<p> But with the proper aspect added to the compilation, (in this
case, <code>figures/Answer1a.java</code>, but you should feel free to use
more evocative names), the test should pass </p>
<blockquote><PRE>
$ ajc -argfile base.lst figures/Answer1a.java
$ java tests.Test1a
.........
Time: 0.089
OK (9 tests)
</PRE></blockquote>
<p> Make sure to pass <code>tests.Test1a</code> before
continuing.
</p>
<h3>b. Trace multiple methods </h3>
<p> Continue your aspect to capture execution of more methods.
</p>
<p> When <code>Point.setX</code> or <code>Point.setY</code> execute,
you should log "set". When one of
</p>
<blockquote><pre>
Point.getX, Point.getY, Line.getP1, Line.getP2
</pre></blockquote>
<p> execute, you should log "get".
</p>
<p> Feel free to use wildcards in your pointcuts.
</p>
<p> For example,
</p>
<blockquote><PRE>
Point p1 = new Point(10, 100);
p1.setY(p1.getX());
System.out.println(Log.getString());
</PRE></blockquote>
<p> should print out "get;set;".
</p>
<p> Make sure to pass the JUnit test <code>tests.Test1b</code>
before continuing. </p>
<h3>c. Use join point information</h3>
<p> Instead of logging <code>"set"</code> and <code>"get"</code>, log
the name of the executing method. For example,
</p>
<blockquote><pre>
Point p1 = new Point(10, 100);
p1.setY(p1.getX());
System.out.println(Log.getString());
</pre></blockquote>
<p> would print out "getX;setY;".
</p>
<p> Make sure to pass the JUnit test <code>tests.Test1c</code>
before continuing. </p>
<h3>d. Refactor and extend</h3>
<p> 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. </p>
<p> In your refactoring, you make sure you continue to pass the JUnit
test <code>tests.Test1c</code>. </p>
<hr>
<h2>2. More advanced Tracing</h2>
<p> If you did some refactoring in the last exercise, restore your
packages to their original contents (which were saved in the
<code>backup</code> directory wherever you unpacked
<code>figures.zip</code>) before starting the next exercises. </p>
<h3>a. Tracing external calls</h3>
<p> 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
<code>java.util</code> package from the <code>figures</code> package.
</p>
<p> You should send a short description of the join point to the log,
with
</p>
<blockquote><pre>
Log.log(thisJoinPointStaticPart.getSignature().toShortString());
</pre></blockquote>
<p> For example, if you compiled your aspect with this
<code>Main</code> class </p>
<blockquote><pre>
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());
}
}
</pre></blockquote>
<p> running it would print out "Set.add(Object);Set.clear();".
</p>
<p> Make sure to pass the JUnit test <code>tests.Test2a</code>
before continuing. </p>
<h3>b. Logging exceptions thrown</h3>
<p> Instead of logging whenever we make an external call to method
from <code>java.util</code>, modify your code to log any
<code>Throwable</code> thrown from such a call from code in the
<code>figures</code> and <code>test</code> packages. </p>
<p> For example, if you compiled your aspect with this
<code>Main</code> class </p>
<blockquote><pre>
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());
}
}
</pre></blockquote>
<p> running it would print out <code>"caught
java.lang.ArrayIndexOutOfBoundsException;"</code>. </p>
<p> Make sure to pass the JUnit test <code>tests.Test2b</code>
before continuing. </p>
<h3>c. Exposing caller and callee</h3>
<p> As an extremely artifical example leading up to the next exercise,
write an aspect that writes to the log:
</p>
<ul>
<li>only when a Group calls move on a Box</li>
</ul>
<p> Log the size of the calling group and the p0 point of the target
box.
</p>
<p> For example, if you compiled your aspect with this
<code>Main</code> class </p>
<blockquote><pre>
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());
}
}
</pre></blockquote>
<p> running it would print out
</p>
<blockquote><pre>
2Point(5, 5);3Point(105,105);
</pre></blockquote>
<p> As you may already have noticed, looking at the included JUnit
tests will also help with the problems. </p>
<p> Make sure to pass the JUnit test <code>tests.Test2c</code>
before continuing. </p>
<h3>d. Tracing only in a control flow</h3>
<p> Write an aspect that logs when the <code>_x</code> and
<code>_y</code> fields on <code>Point</code> are set, but only when in
the process of moving a <code>Box</code>. </p>
<p> Make sure to pass the JUnit test <code>tests.Test2d</code>
before continuing. </p>
<h3>e. Enabling per-object tracing</h3>
<p> 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 <code>Log.traceObject(Object)</code>
(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 <code>if</code> pointcut.</p>
<p> 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 <code>support.Log.logClassName(Class)</code>.
</p>
<p> Make sure to pass the JUnit test <code>tests.Test2e</code>
before continuing. </p>
<h3>f. Refactor and extend</h3>
<p> 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. </p>
<p> In your refactoring, make sure you continue to pass appropriate
the JUnit tests. </p>
<hr>
<!-- page break -->
<h2>3. Check Invariants</h2>
<p> 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.
</p>
<h3>a. Check static invariants</h3>
<p> 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. </p>
<p> 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. </p>
<p> Make sure your program still passes the JUnit test
<code>tests.Test</code> (which it should also pass at the beginning of
all exercises) before continuing. </p>
<h3>b. Check preconditions</h3>
<p> Write an aspect to throw an <code>IllegalArgumentException</code>
whenever an attempt is made to set one of <code>Point</code>'s
<code>int</code> fields to a value that is either too large (greater
than <code>FigureElement.MAX_VALUE</code>) or too small (less than
<code>FigureElement.MIN_VALUE</code>). </p>
<p> Make sure to pass the JUnit test <code>tests.Test3b</code>
before continuing. </p>
<h3>c. Assure input</h3>
<p> Instead of throwing an exception when one of <code>Point</code>'s
<code>int</code> fields are set to an out-of-bounds value, write an
aspect to trim the value into an in-bounds one.
</p>
<p> Make sure to pass the JUnit test <code>tests.Test3c</code>
before continuing. </p>
<h3>d. Check postconditions</h3>
<p> There is a method on the <code>Box</code> class, <code>void
checkBoxness()</code>, 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 <code>checkBoxness()</code> method is called. </p>
<p> When testing this aspect, you may find yourself facing a
<code>StackOverflowException</code>. If so, carefully look at your
pointcuts. </p>
<p> 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:
</p>
<blockquote><PRE>
class Test {
public static void main(String[] args) {
try {
main(args);
} finally {
main(args);
}
}
}
</PRE></blockquote>
<p> Make sure to pass the JUnit test <code>tests.Test3d</code>
before continuing. </p>
<h3>e. Check postconditions</h3>
<p> <code>FigureElement</code>s support the <code>getBounds()</code>
method that returns a <code>java.awt.Rectangle</code> representing the
bounds of the object. An important postcondition of the
<code>move</code> 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.
</p>
<p> You can create a copy of a figure element's bounds rectangle with
<code>new Rectangle(fe.getBounds())</code>, and you can move a bounds
rectangle <code>rect</code> with <code>rect.translate(dx, dy)</code>.
</p>
<p> Make sure to pass the JUnit test <code>tests.Test2f</code>
before continuing. </p>
<h3>f. Refactor and extend</h3>
<p> 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.
</p>
<p> In your refactoring, make sure you continue to pass appropriate
the JUnit tests. </p>
<hr>
<!-- page break -->
<h2>4. Add crosscutting functionality</h2>
<p> If you did some refactoring in the last exercise, restore your
packages to their original contents (which were saved in the
<code>backup</code> directory wherever you unpacked
<code>figures.zip</code>) before starting the next exercises. </p>
<h3>a. Add Colorizing Support</h3>
<p> 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 <code>figures.ColorControl</code> for you to
fill in; that aspect's static methods are called from the JUnit test
<code>tests.Test4a</code>. </p>
<p> Make sure to pass the JUnit test <code>tests.Test4a</code>
before continuing. </p>
<h3>b. Caching group bounds part 1: Manage enclosing groups</h3>
<p> The second crosscutting feature you will be adding is support from
caching the bound objects of <code>Group</code> 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.
</p>
<p> 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.
</p>
<p> Make sure to pass the JUnit test <code>tests.Test4b</code>
before continuing. </p>
<h3>c. Caching group bounds part 2: cache and invalidate</h3>
<p> 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 <code>getBounds()</code> returns the same
rectangle. However, whenever a point moves it should invalidate the
caches of all enclosing groups.
</p>
<p> Make sure to pass the JUnit test <code>tests.Test4b</code>
before continuing. </p>
<h3>d. Refactor and Extend</h3>
<p> 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. </p>
<hr>
</body> </html>