123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- <?xml version="1.0" encoding="iso-8859-1"?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-
- <!-- This file represents the Exercises for the hands-on AspectJ
- tutorial. It is commonly checked into CVS with identifying
- information for the latest conference (such as presenters
- and publication information).
-
- When you use it for your own purposes, don't forget to
- modify at the very least anything that says
- id="copyright" or class="presenter". If you're in an A4
- country, don't forget to modify the paper size.
-
- The gif included at the end is somewhat fragile,
- so be careful with different paper sizes.
-
- TODO: There is currently something weird about PDF generation
- from this: If generated from a windows machine, it will
- generate mac-unfriendly PDF because of the requested windows
- font. If the PDF is only used for immediate printing,
- that's fine, but if it's used for distribution, bad.
- -->
-
- <head>
- <title>Hands-on Programming with AspectJ® — Exercises</title>
- <style type="text/css">
- div.instruction { padding: 0.5em; border-width: 1px; border-style: solid }
- body { background-color: #FFF; margin: 2em }
- body { font-family: "Gill Sans MT", "Gill sans", "Trebuchet ms", Verdana, sans-serif; }
- .newpage { page-break-before: always }
- pre { margin-left: 1em; border-left-style: solid; border-width: 1px; padding-left: 1em;}
- h2 { margin-top: 4ex; }
- h3 { margin-top: 4ex; border-bottom-style: solid; border-width: 1px }
- .presenter { text-align: right }
- @page {
- size: 8.5in 11in;
- margin: 3in;
- marks: cross
- }
- @media print {
- body { font-size: 10pt }
- #copyright {
- font-family: "Times New Roman", "Times Roman", fixed;
- font-size: 8pt;
- display: normal;
- position: absolute;
- bottom: 1in;
- border-style: none
- }
- }
- @media screen {
- #copyright { display: none }
- }
- </style>
- </head>
-
- <body>
-
- <h1>Hands-on Programming with AspectJ<sup>®</sup></h1>
-
- <div class="presenter">Erik Hilsdale</div>
- <div class="presenter">Mik Kersten</div>
- <div class="presenter">http://www.eclipse.com/aspectj</div>
-
- <h2>Overview</h2>
-
- <p> In this tutorial you will solve some canonical programming
- tasks using AspectJ. The tasks progress from writing
- non-functional, development-only aspects to writing aspects that
- augment a deployed program with crosscutting features. This
- follows the same progression most users see in their own adoption
- of AspectJ. </p>
-
- <p> Since this is a hands-on tutorial, you will be working with a
- live AspectJ distribution. The example code we will be working
- with is a simple figure editor, along with JUnit tests for each
- exercise. We will break up into groups of two to three people
- per computer to foster discussion within the group as well as
- with the presenters. </p>
-
- <p> If you have a laptop running a recent version of Windows,
- MacOS or Linux, feel free to bring it along. We will provide CDs
- and other installation media for a standalone AspectJ system,
- including the figure editor code these exercises are based on and
- unit tests for the exercises. If you don't have a laptop with
- you, don't worry about it. </p>
-
- <p> These notes consist of four sections of exercises, a quick
- reference to AspectJ syntax, and a UML diagram of a figure editor
- program. </p>
-
- <div class="instruction"> If you receive these tutorial notes
- early, feel free to have a quick look, especially at the UML
- diagram and quick reference. But you'll be cheating yourself if
- you try to do the exercises early; you'll learn a lot more by
- working through it in groups during the tutorial proper. </div>
-
- <!--
- This space is used for a copyright that appears on the
- bottom of the _printed_ page. It's suppressed when viewed on
- a computer screen by the stylesheet.
-
- <div id="copyright">
- Copyright is held by the author/owner(s). <br />
- OOPSLA’04, October 24-28, 2004, Vancouver, British Columbia, Canada <br />
- 2004 ACM 04/0010
- </div>
- -->
-
- <h3 class="newpage">Command-line usage</h3>
-
- <p> While the AspectJ system is well integrated with a number of
- IDEs, it can also be used as a command-line compiler. The
- standalone package we provide (containing the tests, the base
- code, JUnit, and a distribution of AspectJ) needs 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 <code>setpaths</code> or <code>setpaths.bat</code>, as
- appropriate.
- </p>
-
- <p> Each time you open a new shell window run
- <code>setpaths.bat</code> or <code>source setpaths</code> to
- export some other needed environment variables. </p>
-
- <p> In general, all the files in the program are listed in
- <code>base.lst</code>, 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
- <code>base.lst</code>, either in an IDE or with </p>
-
- <pre>
- $ ajc -argfile base.lst
- </pre>
-
- <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>
-
- <pre>
- > copy answers/Answer.java answers/2a.java (Windows)
- $ cp answers/Answer.java answers/2a.java (Unix)
- </pre>
-
- <p> After building the system, you should invoke Java on the compiled
- test class. On the command-line, this this would be </p>
-
- <pre>
- $ java tests.Test2a
- </pre>
-
- <p> The default test, <code>tests.CoreTest</code>, performs some
- rudimentary tests on figure elements, and so is a useful test to run
- periodically. You should also look at the JUnit tests for each
- exercise as you do it. </p>
-
- <p> Again, we will be looking at solutions and having discussion,
- which is much more difficult without incremental solutions. So
- when you go from one exercise to the next, <strong>save your
- work</strong> in a file before going on to the next exercise
- even if you plan to duplicate some code. </p>
-
- <div class="instruction"> When we give examples of execution in
- these exercises we will show the command-line use, but of course
- you should use the appropriate compile and execute tools if you
- are using the AspectJ browser, Emacs, or Eclipse. </div>
-
- <!-- ============================== -->
-
- <h2 class="newpage">1. Static Invariants</h2>
-
- <p> The easiest way to get started with AspectJ is to use it to
- enforce static invariants.
- </p>
-
- <h3>1.a. Find old tracing</h3>
-
- <div class="instruction"> <strong>Sample Exercise</strong>: The
- main point of this exercise is to make sure your configuration
- works. Type in the answer below into your answer file, make sure
- you get the desired compile-time error, remove the offending
- line, and make sure you pass the JUnit test. </div>
-
- <p> <strong>Task:</strong> Signal an error 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 far longer than is necessary. Type in the aspect
- below to signal an error at compile time if this mistake is made.
- </p>
-
- <p> <strong>Answer:</strong>
- </p>
-
- <pre>
- package answers;
-
- import figures.*;
-
- aspect Answer1a {
- declare error
- : get(java.io.PrintStream System.out) && within(figures..*)
- : "illegal access to System.out";
- }
- </pre>
-
- <p> When you use this on the given system, you'll find one incorrect
- trace in <code>SlothfulPoint</code>.
- </p>
-
- <pre>
- $ ajc -argfile base.lst
- ./figures/SlothfulPoint.java:38 illegal access to System.out
-
- 1 error
- </pre>
-
- <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>
-
- <p> After you have successfully used this aspect, edit your
- program to remove the illegal tracing call. </p>
-
- <p> Make sure your program still passes the JUnit test
- <code>tests.CoreTest</code> (which it should also pass at the beginning of
- all exercises) before continuing. </p>
-
- <pre>
- $ java tests.CoreTest
- ....
- Time: 0.03
-
- OK (4 tests)
- </pre>
-
- <h3 class="newpage">1.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>,
- the <code>void set*(..)</code> pattern
- </p>
-
- <p> One common coding convention is that no private field should
- be assigned to outside of setter methods. Write an aspect to
- signal a warning at compile time for these illegal assignment
- expressions. </p>
-
- <p> This is going to look like
- </p>
-
- <pre>
- aspect A {
- declare warning: <em><pointcut here></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.CoreTest</code> before continuing. Make sure you get eleven
- warnings from this. Wait to fix them until the next exercise. </p>
-
- <h3>1.c. Refine setters mandate</h3>
-
- <p> <strong>Task:</strong> Allow assignmnents inside of constructors.
- </p>
-
- <p> <strong>Tools:</strong> the <code>new(..)</code> pattern</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 assigned to outside of
- setter methods <em>or constructors</em>. Modify your answer 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:37 bad field set
- .\figures\Point.java:38 bad field set
-
- 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.CoreTest</code> before continuing. </p>
-
- <div class="instruction"> Congratulations, 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.
- Try to resist moving on to the next section until we discuss
- solutions as a group. </div>
-
- <!-- ============================== -->
-
- <h2 class="newpage">2. Dynamic invariants</h2>
-
- <p> The next step in AspectJ adoption is often to augment a test suite
- by including additional dynamic tests.
- </p>
-
- <div class="instruction"> Tutorial attendees typically progress
- at different speeds through these exercises. Throughout this
- tutorial, if you finish early, see what the people around you are
- doing and if they need help. Don't help them out of charity,
- help them out of naked self-interest—we promise you'll learn a
- lot about using AspectJ by explaining it. </div>
-
- <h3>2.a. Check a simple precondition</h3>
-
- <div class="instruction"> <strong>Sample Exercise</strong>: We've
- provided the answer to this exercise to get you started. Feel
- free to think a bit, but don't get stuck on this one. </div>
-
- <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>
-
- <pre>
- $ java tests.Test2a
- .F..F....
- Time: 0.04
- There were 2 failures:
- 1) testTooSmall(tests.Test2a)junit.framework.AssertionFailedError: should have thrown IllegalArgumentException
- 2) testMove(tests.Test2a)junit.framework.AssertionFailedError: should have thrown IllegalArgumentException
-
- FAILURES!!!
- Tests run: 7, Failures: 2, Errors: 0
- </pre>
-
- <p> But after compiling in the aspect...
- </p>
-
- <pre>
- $ ajc -argfile base.lst answers/Answer.java
-
- $ java tests.Test2a
- .......
- Time: 0.04
-
- OK (7 tests)
- </pre>
-
- <p> <strong>Answer:</strong>
- </p>
-
- <pre>
- package answers;
-
- import figures.*;
-
- aspect Answer2a {
- before(int newValue): set(int Point.*) && args(newValue) {
- if (newValue < 0) {
- throw new IllegalArgumentException("too small");
- }
- }
- }
- </pre>
-
- <h3>2.b. Check another precondition</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test2b</code>. </p>
-
- <p> <strong>Tools: </strong> <code>call</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>2.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>
-
- <h3>2.d. Assure input</h3>
-
- <p> <strong>Task: </strong> Pass <code>tests.Test2d</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 is set to a negative value, write an aspect
- to trim the value to zero. 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>
-
- <p> This is going to look something like
- </p>
-
- <pre>
- aspect A {
- void around(int val): <var><Pointcut></var> {
- <var><Do something with val></var>
- proceed(val);
- }
- }
- </pre>
-
- <h3 class="newpage">2.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 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>2.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>
-
- <!-- ============================== -->
-
- <h2 class="newpage">3. Tracing</h2>
-
- <p> Tracing is one of the classic AspectJ applications, and is often
- the first where AspectJ is used on deployed code.
- </p>
-
- <h3>3.a. Simple tracing</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test3a</code>.</p>
-
- <p> <strong>Tools:</strong>
- <code>Log.write(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> (this is in the <code>support</code> package, so
- remember to import it into your answer aspect). Write a message
- into the log with the static <code>write(String)</code> method.</p>
-
- <h3>3.b. Exposing a value</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test3b</code>.</p>
-
- <p> <strong>Tools:</strong> <code>target</code>
- </p>
-
- <p> AspectJ can expose the target object at a join point for tracing.
- In this exercise, you will print not only the join point information,
- but also the target object, with the form
- </p>
-
- <pre>
- <em>thisJoinPointInfo</em> at <em>targetObject</em>
- </pre>
-
-
- <h3>3.c. More specialized logging</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test3c</code>.</p>
-
- <p> <strong>Tools:</strong> <code>args</code>.
- </p>
-
- <p> Write an aspect to log whenever a <code>Point</code> is added to a
- group. The <code>args</code> pointcut allows you to select join points
- based on the type of a parameter to a method call. </p>
-
- <p> Look at the test case to see the trace message we expect you
- to write in the log.
- </p>
-
- <h3 class="newpage">3.d. Logging extended to checking an invariant</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test3d</code>.</p>
-
- <p> <strong>Tools:</strong> <code>inter-type field declaration</code>
- </p>
-
- <p> Make sure that a <code>Point</code> is never added to more
- than one <code>Group</code>. To do so, associate a boolean flag
- with each <code>Point</code> using an inter-type declaration,
- such as </p>
-
- <pre>
- boolean Point.hasBeenAdded = false;
- </pre>
-
- <p> Check and set this flag with the same kind of advice from your
- answer to problem (c). Throw an <code>IllegalStateException</code> if
- the point has already been added.
- </p>
-
- <h3>3.e. Better error messages for 3.d.</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test3e</code>.</p>
-
- <p> Extend your solution to problem (d) by using the string
- representation of the Point's containing group as the <code>msg</code>
- part of the <code>IllegalStateException</code>. </p>
-
- <!-- ============================== -->
-
- <h2 class="newpage">4. Caching</h2>
-
- <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>
-
- <div class="instruction"> <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. </div>
-
- <h3>4.a. Make a constant override</h3>
-
- <p> <strong>Task:</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>4.b. Make a constant cache</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test4b</code>.
- </p>
-
- <p> <strong>Tools:</strong> inter-type field.
- </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>4.c. Invalidate, part 1</h3>
-
- <p> <strong>Task:</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 class="newpage">4.d. Invalidate, part 2</h3>
-
- <p> <strong>Task:</strong> Pass <code>tests.Test4d</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>4.e. Invalidate, part 3</h3>
-
- <p> <strong>Task:</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>
-
- <div class="instruction">
-
- <p> Congratulations! Not only have you learned about how to
- program in AspectJ, you have worked through exercises paralleling
- a common AspectJ adoption strategy. You should be able to
- pick up AspectJ and use it to improve your own software's
- crosscutting modularity. </p>
-
- <p> You can find the current binaries, source, documentation and
- an active user community for AspectJ at</p>
-
- <blockquote>
- http://www.eclipse.org/aspectj
- </blockquote>
-
- </div>
-
- <img style="newpage" src="figures_classes.gif" height="900" alt="" />
-
- </body> </html>
|