aboutsummaryrefslogtreecommitdiffstats
path: root/docs/teaching/exercises/index.html
blob: 500915e99f56b6c19bcd76a611d241010c134a90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html> <head>
<title>AspectJ Tutorial Exercises</title>
</head>

<body bgcolor="white">
<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> We have made available a package that includes the tests, the base
code, JUnit, and a distribution of AspectJ.  All it needs is
information about where Java lives (so set your JAVA_HOME environment
variable).  It assumes that you unzip it in c:\ (on Windows) or in
your home directory (on Linux): If you put it somewhere else, edit
setpaths or setpaths.bat, as appropriate.  Once all this is done, run
<code>setpaths.bat</code> or <code>source setpaths</code> to export
some other needed environment variables. </p>

<p> All the files in the program are listed in base.lst, including
test cases and an empty answer aspect,
<code>answers/Answer.java</code>.  Therefore, if you write your
answers there, all you need to do is compile base.lst, either in an
IDE or with </p>

<blockquote><PRE>
$ ajc -Xlint -argfile base.lst
</PRE></blockquote>

<p> Before you move onto another exercise, though, make sure to copy
your answer into a different file so we can discuss the answers
together:
</p>

<blockquote><PRE>
&gt; copy answers/Answer.java answers/2a.java  (Windows)
$ cp answers/Answer.java answers/2a.java    (Linux)
</PRE></blockquote>

<p> If you want to put your answer in a different file, say,
<code>answers/Answer2a.java</code>, you can compile with </p>

<blockquote><PRE>
$ ajc -Xlint -argfile base.lst answers/Answer2a.java
</PRE> </blockquote>

<p> In any case, after building the system, you should invoke Java on
the compiled test class.  On the command-line, this this would be </p>

<blockquote><PRE>
$ java tests.Test2a
</PRE> </blockquote>

<p> (For these exercises, when we give examples of execution we will
show the command-line use, but of course if you are using JBuilder,
Forte/NetBeans, Emacs, or Eclipse, use the appropriate compile and
execute tools.)  </p>

<p> The default test, <code>tests.Test</code>, performs some
rudimentary tests on figure elements, and so is a useful test to run
periodically.  Looking at the JUnit tests for each exercise may also
be helpful. </p>

<p> Again, ae will be looking at some solutions and having discussion,
which is much more difficult without incremental solutions.  So when
you go from one exercise to the next, make sure to save your work in a
file and go on to work in a different file, even if you plan to
duplicate some code.  </p>

<hr>
<!-- page break  -->
<h2>1. Static Invariants</h2>

<h3>a. Catch old tracing</h3>

<p> <strong>Sample Exercise</strong>: The main point of this exercise
is to make sure your configuration works.  We have provided the
answer to this exercise below, so XXX.  You need not go through the
thought process of fixing this </p>

<p> <strong>Task:</strong> Signal a warning for calls to
<code>System.out.println</code>. 
</p>

<p> The way that we are all taught to print "hello world" from Java is
to use <code>System.out.println()</code>, so that is what we typically
use for one-off debugging traces.  It's a common mistake to leave
these in your system longer than is necessary.  Type in the aspect
below to forces an error at compile time if this mistake is made.
</p>

<p> When you use this on the given system, you'll find one incorrect
trace in <code>SlothfulPoint</code>.  
</p>

<blockquote><PRE>
$ ajc -argfile base.lst
./figures/SlothfulPoint.java:29:9: illegal access to System.out
        System.out.println("Slothful moving");
               ^
1 errors
</PRE></blockquote>

<p> Remove the illegal tracing call. 
</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>

<blockquote><PRE>
$ java tests.Test
....
Time: 0.076

OK (4 tests)
</PRE></blockquote>

<p> <strong>Answer:</strong>
</p>

<blockquote><PRE>
package answers;

import figures.*;

aspect Answer1a {
    declare error
        : get(java.io.PrintStream System.out) &amp;&amp; within(figures..*)
        : "illegal access to System.out";
}
</PRE></blockquote>

<p> Note that this answer does not say that the <em>call</em> to the
<code>println()</code> method is incorrect, rather, that the field get
of the <code>out</code> field is illegal.  This will also catch those
users who bind System.out to a static field to save typing.  </p>


<h3>b. Mandate setters</h3>

<p> <strong>Task:</strong> Signal a warning for assignments outside
of setter methods.  </p>

<p> <strong>Tools:</strong> <code>set</code>, <code>withincode</code>,
signature <code>void set*(..)</code>
</p>

<p> One common coding convention is that no private field should be
set outside of setter methods.  Write an aspect to warn at compile
time when such an illegal assignment expression exists.  </p>

<p> This is going to look like
</p>

<pre>
aspect A {
    declare warning: <em>&lt;pointcut here&gt;</em> : "bad field set";
}
</pre>

<p> where the pointcut picks out join points of private field sets
outside of setter methods.  "Outside", here, means that the code for
the assignment is outside the <em>text</em> of the setter. 

<p> Make sure your program still passes the JUnit test
<code>tests.Test</code> before continuing, and that you see all of the
following warning messages.  Make sure you get 11 warnings from this.
Wait to fix them until the next exercise.  </p>

<h3>c. Refine setters mandate</h3>

<p> <strong>Task:</strong> Allow assignmnents inside of constructors.
</p>

<p> <strong>Tools:</strong> signature <code>new(..)</code> </p>

<p> Look at some of the warnings from the previous exercise.  Notice
that a lot of them are from within constructors.  Actually, the common
coding convention is that no private field should be set outside of
setter methods <em>or constructors</em>.  Modify your answer (in a new
file) to signal an actual error at compile time (rather than just a
warning) when such an illegal assignment expression exists.  </p>

<p>You'll want to add another <code>withincode</code> primitive
pointcut to deal with the constructors. 
</p>

<p>After you specify your pointcut correctly, you'll still find that
the convention is violated twice in the figures package.  You should see
the following two errors:</p>

<pre>
.\figures\Point.java:28:9: bad field set
        _x += dx;
        ^
.\figures\Point.java:29:9: bad field set
        _y += dy;
        ^
2 errors
</pre>

<p>Rewrite these two occurrences so as not to violate
the convention.  Make sure your program still passes the JUnit test
<code>tests.Test</code> before continuing.  </p>

<h3>d. Congratulatoins</h3>

<p> You've taken your first steps.  At this point, check the people to
your left and right.  If they're stuck somewhere, see if you can help
them.  </p>

<!-- ============================== -->

<hr />

<h2>2. Dynamic invariants</h2>


<h3>a. Check a simple precondition</h3>

<p> <strong>Sample Exercise</strong>: The main point of this exercise
is to make sure your configuration works.  We have provided the
answer to this exercise below, so XXX.  You need not go through the
thought process of fixing this </p>


<p> <strong>Task:</strong> Pass <code>tests.Test2a</code>.
</p>

<p> <strong>Tools:</strong> <code>args</code>, <code>before</code>
</p>

<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 less than zero.  </p>

<p> This should make the test case of <code>tests.Test2a</code> pass,
which wouldn't without your aspect.  So before compiling in the
aspect, 
</p>

<blockquote><PRE>
$ ajc -Xlint -argfile base.lst

$ java tests.Test2a
.F.F.F....
Time: 0.099
There were 3 failures:
1) testTooSmall(tests.Test2a)junit.framework.AssertionFailedError: should have thrown IllegalArgumentException
2) testTooBig(tests.Test2a)junit.framework.AssertionFailedError: should have thrown IllegalArgumentException
3) testMove(tests.Test2a)junit.framework.AssertionFailedError: should have thrown IllegalArgumentException

FAILURES!!!
Tests run: 7,  Failures: 3,  Errors: 0
</PRE></blockquote>

<p> But after compiling in the aspect...
</p>

<blockquote><PRE>
$ ajc -Xlint -argfile base.lst

$ java tests.Test2a
.......
Time: 0.097

OK (7 tests)
</PRE></blockquote>

<p> <strong>Answer:</strong>  
</p>

<blockquote><PRE>
package answers;

import figures.*;

aspect Answer2a {
    before(int newValue): set(int Point.*) &amp;&amp; args(newValue) {
        if (newValue < 0) {
            throw new IllegalArgumentException("too small");
        } 
    }
}
</PRE></blockquote>

<h3>b. Check another precondition</h3>

<p> <strong>Task:</strong> Pass <code>tests.Test2b</code>. </p>

<p> <code>Group</code> is a <code>FigureElement</code> class that
encapsulates groups of other figure elements.  As such, only actual
figure element objects should be added to <code>Group</code> objects.
Write an aspect to throw an <code>IllegalArgumentException</code>
whenever <code>Group.add()</code> is called with a <code>null</code>
value. </p>

<p> Look at <code>tests/Test2b.java</code> to see exactly what we're
testing for. </p>

<h3>c. Check yet another precondition</h3>

<p> <strong>Task:</strong> Pass <code>tests.Test2c</code>. </p>

<p> <strong>Tools:</strong> <code>target</code>
</p>

<p> Another constraint on a well-formed group is that it should not
contain itself as a member (though it may contain other groups). Write
an aspect to throw an <code>IllegalArgumentException</code> whenever
an attempt is made to call <code>Group.add()</code> on a
<code>null</code> value, or on the group itself. </p>

<p> You will want to use a <code>target</code> pointcut to expose the
<code>Group</code> object that is the target of the <code>add</code>
call. 
</p>

XXX RENUMBER LATER

<h3>d. Assure input</h3>

<p> <strong>Task: </strong> Pass <code>tests.Test2g</code>.
</p>

<p> <strong>Tools: </strong>  around advice
</p>

<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.  You'll want to use
<code>around</code> advice that exposes the new value of the field
assignment with an <code>args</code> pointcut, and
<code>proceed</code> with the trimmed value. </p>

<h3>e. Check a postcondition</h3>

<p> <strong>Task: </strong>  Pass <code>tests.Test2e</code>
</p>

<p> <strong>Tools: </strong>  around advice
</p>

<p> A postcondition of a <code>Point</code>'s <code>move</code>
operation is that the <code>Point</code>'s coordinates should change.
If a call to move move didn't actually move a point by the desired
offset, then the point is in an illegal state and so an
<code>IllegalStateException</code> should be thrown.
</p>

<p> Note that because we're dealing with how the coordinates change
during move, we need some way of getting access to the coordinates
both before <em>and</em> after the move, in one piece of advice.  </p>

<h3>f. Check another postcondition</h3>

<p> <strong>Task: </strong>  Pass <code>tests.Test2f</code>
</p>

<p> <strong>Tools:</strong> the <code> Rectangle(Rectangle)</code>
constructor, the <code>Rectangle.translate(int, int)</code> method.
</p>

<p> <code>FigureElement</code> objects have a <code>getBounds()</code>
method that returns a <code>java.awt.Rectangle</code> representing the
bounds of the object.  An important postcondition of the general
<code>move</code> operation on a figure element 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 --
throw an <code>IllegalStateException</code> if it is violated.  </p>

<h3>Help Yourself by Helping Others</h3>

<p> At this point, check the people to your left and right.  If
they're stuck somewhere, see if you can help them.  </p>

<!-- ============================== -->

<hr />
<!-- page break -->

<h2>3. Logging</h2>

<h3>d. Check a simple postcondition</h3>

<p> One of the simplest postconditions to check is that a setter
actually sets its value.  Write an aspect that throws a
<code>java.lang.RuntimeException</code> if, after calling
<code>setX()</code> on <code>SlothfulPoint</code> objects,
<code>getX()</code> doesn't return the new value. </p>

<p> You'll want to use an <code>args</code> pointcut to expose the
argument to <code>setX()</code> and a <code>target</code> pointcut to
expose the <code>SlothfulPoint</code> object itself (so you can later
call <code>getX()</code> on it).  
</p>

<p> An interesting question to think about for discussion is whether
this postcondition should apply when getX() throws an exception, or
when it returns normally, or both?  </p>

<p> With this aspect in place, your code should pass
<code>tests.Test2d</code>. 
</p>


<h3>e. Check invariant</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 (i.e., they form a
rectangle).  Write an aspect that will make sure that after every time
the <code>void move(int, int)</code> method on <code>Box</code> is
called, that you also call <code>Box.checkBoxness()</code> to ensure
that the <code>move</code> didn't break this invariant. </p>

<p> With this aspect in place, your code should pass
<code>tests.Test2e</code>. 
</p>

<h3>f. Refine your invariant</h3>

<p> <code>move</code> is not the only interesting method on
<code>Box</code>.  It may be that a box gets malformed between calls
to <code>move</code>.  So instead of checking boxness only 
after the <code>move</code> method of <code>Box</code>, check
after the call to every one of <code>Box</code>'s public methods.
</p>

<p> When testing this aspect, you may find yourself facing a
<code>StackOverflowException</code>.  If so, carefully look at your
pointcuts.  Needless to say, there should not be an infinite loop in
your program.  You might want to look at using a <code>within</code>
pointcut for a filter. </p>

<p> (You might even find that this test case aborts with no message,
i.e., 
</p>

<blockquote><pre>
$ java tests.test2f
.
$
</pre></blockquote>

<p> this is a bug in Sun's JVM where a particular stack overflow
causes the VM to abort.)
</p>

<p> Make sure to pass the JUnit test <code>tests.Test2f</code>
before continuing.  </p>

==================================================

<p> The crosscutting feature you will be adding in part (4) will be
support for caching the bound objects of <code>Group</code> figure
elements, which may be costly to compute.  On the way to that, though,
it's useful to explore the system with some tracing aspects.  </p>

<h3>a. Simple logging</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test3a</code>.</p>

<p> <strong>Tools:</strong> <code>Log.log(String)</code>, 
		<code>thisJoinPoint.toString()</code>, <code>execution</code>,
		<code>within</code>
</p>

<p> Write an aspect to log the execution of all public methods
in the figures package.  To do this, use the utility class
<code>Log</code> (with an import from <code>support.Log</code>) 
and call <code>Log.log(String)</code></p>

<h3>b. Simple logging</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test3b</code>.</p>

<p> <strong>Tools:</strong> <code>target</code>
</p>


<h3>c. More specialized logging</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test3c</code>.</p>

<p> <strong>Tools:</strong>
</p>

<p> Write an aspect to log whenever a <code>Point</code> is added to
a group (including initially).  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("adding Point")
</PRE></blockquote>

<p> This will write the string "adding Point", followed by a semicolon
terminator, to the Log. For example, with your aspect enabled, </p>

<blockquote><PRE>
Point p1 = new Point(10, 100);
Point p2 = new Point(10, 100);
Group g = new Group(p1);
g.add(p2);
System.out.println(Log.getString());
</PRE></blockquote>

<p> should print out "adding Point;adding Point;".  
</p>

<p> <em>Hint: The <code>args</code> pointcut allows you to select join points
based on the type of a parameter to a method call. </em> </p>

<h3>d. Logging extended to checking an invariant</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test3d</code>.</p>

<p> <strong>Tools:</strong> <code>inter-type field declaration</code>
</p>

<p>Make sure that a Point is never added to more than one Group.</p>


<h3>e. Better error messages for 3d</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test3e</code>.</p>

<p> <strong>Tools:</strong>
</p>

<p>Make sure that a Point is never added to more than one Group.
Include the Group that the Point was previously a part of in the
thrown exception.</p>

<h3>c. Keeping track of state</h3>

<p> In this exercise, perform the tracing from part (a), but also log
the enclosing group, if any, of the moving point.  You can use an
inter-type declaration inside your aspect to associate a
<code>Group</code> field with <code>Point</code> objects, and then
work with that field, setting it appropriately when the
<code>Point</code> is added to a <code>Group</code> (at the same join
points you were tracing in part b).  So </p>

<blockquote><PRE>
Point p1 = new Point(10, 100);
p1.move(0, 0);
System.out.println(Log.getString());
</PRE></blockquote>

<p> should print out "moving as a part of null;", but   
</p>

<blockquote><PRE>
Point p1 = new Point(10, 100);
Group g = new Group(p1);
p1.move(0, 0);
System.out.println(Log.getString());
</PRE></blockquote>

<p> should print out "moving as a part of Group(Point(10, 100));",
which you can do by using the toString() method already defined on
Group.  </p>

<p> <em> Hint: This exercise combines the tracing from parts a and b.
If you start with an aspect that includes the solutions to those
previous exercises, you'll be most of the way there. </em> </p>

<p> Test this with the JUnit test case <code>tests.Test3c</code>.


<h3>Help Yourself by Helping Others</h3>

<p> At this point, check the people to your left and right.  If
they're stuck somewhere, see if you can help them.  </p>


<!-- ============================== -->

<hr />

<!-- page break -->
<h3>4.  Caching</h3>

<p> Computation of the bounding box of <code>Group</code> objects
needs to deal with all aggregate parts of the group, and this
computation can be expensive.  In this section, we will explore
various ways of reducing this expense. </p>

<p> <strong>Optional</strong>: In all of these exercises, you should
only deal with points that are added directly to Groups, rather than
those that are added "indirectly" through Lines and Boxes.  You should
handle those points contained in Lines and Boxes only if time permits.
</p>

<h3>a. Make a constant override</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test4a</code>.</p>

<p> <strong>Tools:</strong> <code>around</code>, 
		<code>FigureElement.MAX_BOUNDS</code>
</p>

<p> <code>Group</code>'s <code>getBounds()</code> method could be
understood to be a conservative approximation of the bounding box of a
group.  If that is true, then it would be a legal (and much faster)
implementation of <code>getBounds()</code> to simply always return a
rectangle consisting of the entire canvas.  The entire canvas is returned
by the static method <code>FigureElement.MAX_BOUNDS</code>.
</p>

<p> Write an aspect to implement this change.  You can override
<code>Group</code>'s <code>getBounds()</code> method entirely with
around advice intercepting the method.  
</p>

<h3>b. Make a constant cache</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test4b</code>.
</p>

<p> <strong>Tools:</strong> <code>private Rectangle Group.mumble;</code>
</p>

<p> Instead of making the (very) conservative approximation of
<code>getBounds()</code> from part (a), write an aspect instead that
remembers the return value from the first time
<code>getBounds()</code> has been called on a <code>Group</code>, and
returns that first <code>Rectangle</code> for every subsequent
call. </p>

<p> <em>Hint: You can use an inter-type declaration to keep some
state for every <code>Group</code> object.</em> </p>


<h3>c. Invalidate, part 1</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test4c</code>.
</p>

<p> <strong>Tools:</strong> <code>before</code>
</p>

<p> While caching in this way does save computation, it will lead to
incorrect bounding boxes if a <code>Group</code> is ever moved.
Change your aspect so that it invalidates the cache whenever the
<code>move()</code> method of <code>Group</code> is called. 
</p>

<h3>d. Invalidate, part 2</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test4d</code>.</p>

<p> <strong>Tools:</strong> <code>your solution to 3c</code></p>

<p> Of course, part (c) didn't really solve the problem.  What if a
<code>Point</code> that is part of a <code>Group</code> moves?
Whenever either of a Point's fields are set it should invalidate the
caches of all enclosing groups.  Use your solution to problem 3c to
modify your invalidation criteria in this way, but note that this is
slightly different than the problem in 3c: Here you care about fields,
where there you cared about method calls. </p>

<h3>e. Invalidate, part 3</h3>

<p> <strong>Problem:</strong> Pass <code>tests.Test4e</code>.</p>

<p> <strong>Tools:</strong> <em>You're on you're own</em></p>

<p> Did you really do part (d) correctly?  Run the JUnit test
<code>tests.Test4e</code> to see.  If you pass, congratulations, now
go help other people.  Otherwise, you have fallen prey to our cruel
trap: Remember that whenever a point moves it should invalidate the
caches of <em>all</em> enclosing groups.  </p>

<hr>
</body> </html>