diff options
author | wisberg <wisberg> | 2003-03-10 09:34:37 +0000 |
---|---|---|
committer | wisberg <wisberg> | 2003-03-10 09:34:37 +0000 |
commit | 1c3c02518fc13f022230acac07db10649776955b (patch) | |
tree | 8e730458c29ea9b66c63b9e6f2fc9a9f85e4907b | |
parent | 12bf4ac8aab5d465f50be0cf60a46937559a00cc (diff) | |
download | aspectj-1c3c02518fc13f022230acac07db10649776955b.tar.gz aspectj-1c3c02518fc13f022230acac07db10649776955b.zip |
manual test for experimental ant task option to copy non-.class file contents of input jars to the output jar after each compile or recompile.
Currently showing VerifyError on recompile -- need to investigate further.
-rw-r--r-- | taskdefs/testdata/incTest/incTest.xml | 70 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/one/InjarOneMain.java | 6 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/one/one.properties | 1 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/one/overview.gif | bin | 0 -> 2576 bytes | |||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/one/subdir/examples.xml | 2343 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/two/InjarTwoMain.java | 6 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/two/twoSubdir/overview2.gif | bin | 0 -> 2576 bytes | |||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/two/twoSubdir/subdir/twoexamples.xml | 2343 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/injarSrc/two/twoSubdir/two.properties | 1 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/readme-incTest.html | 97 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/src/TraceMains.java | 14 | ||||
-rw-r--r-- | taskdefs/testdata/incTest/src/packageOne/Main.java | 8 |
12 files changed, 4889 insertions, 0 deletions
diff --git a/taskdefs/testdata/incTest/incTest.xml b/taskdefs/testdata/incTest/incTest.xml new file mode 100644 index 000000000..615cac04d --- /dev/null +++ b/taskdefs/testdata/incTest/incTest.xml @@ -0,0 +1,70 @@ + +<!-- to test incremental task, run setup then test --> +<project default="test" basedir="."> + <target name="init"> + <property name="td" + location="${basedir}/../.."/> + <property name="test.dir" + location="${td}/testdata/incTest"/> + <taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"> + <classpath> + <pathelement path="${td}/bin"/> + <pathelement path="${td}/src"/> + <pathelement path="${td}/../asm/bin"/> + <pathelement path="${td}/../bridge/bin"/> + <pathelement path="${td}/../org.aspectj.ajdt.core/bin"/> + <pathelement path="${td}/../org.eclipse.jdt.core/bin"/> + <pathelement path="${td}/../util/bin"/> + <pathelement path="${td}/../weaver/bin"/> + <pathelement path="${td}/../lib/eclipse2.0/jdtDepends.jar"/> + <pathelement path="${td}/../lib/bcel/bcel.jar"/> + </classpath> + </taskdef> + </target> + + <target name="setup" depends="init" + description="create input jars"> + <mkdir dir="${test.dir}/injars"/> + <iajc outjar="${test.dir}/injars/oneClasses.jar" + classpath="${td}/../lib/test/aspectjrt.jar" + sourceroots="${test.dir}/injarSrc/one"/> + <iajc outjar="${test.dir}/injars/twoClasses.jar" + classpath="${td}/../lib/test/aspectjrt.jar" + sourceroots="${test.dir}/injarSrc/two"/> + <zip zipfile="${test.dir}/injars/one.jar"> + <zipfileset src="${test.dir}/injars/oneClasses.jar"/> + <fileset dir="${test.dir}/injarSrc/one" + includes="**/*" + excludes="**/*.java"/> + </zip> + <zip zipfile="${test.dir}/injars/two.jar"> + <zipfileset src="${test.dir}/injars/twoClasses.jar"/> + <fileset dir="${test.dir}/injarSrc/two" + includes="**/*" + excludes="**/*.java"/> + </zip> + <delete> + <fileset dir="${test.dir}/injars" + includes="*Classes.jar"/> + </delete> + </target> + + <target name="test" depends="init"> + <mkdir dir="${test.dir}/output"/> + <property name="tag.file" + location="${test.dir}/output/tagFile.txt"/> + <echo message="edit to recompile, delete to quit" + file="${tag.file}"/> + <echo message="update to recompile, delete to quit: ${tag.file}"/> + <iajc outjar="${test.dir}/output/outjar.jar" + injars="${test.dir}/injars/one.jar;${test.dir}/injars/two.jar" + classpath="${td}/../lib/test/aspectjrt.jar" + sourceroots="${test.dir}/src" + xCopyInjars="true" + tagFile="${tag.file}" + verbose="on"/> + <!-- + incremental="true" + --> + </target> +</project>
\ No newline at end of file diff --git a/taskdefs/testdata/incTest/injarSrc/one/InjarOneMain.java b/taskdefs/testdata/incTest/injarSrc/one/InjarOneMain.java new file mode 100644 index 000000000..0eba13066 --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/one/InjarOneMain.java @@ -0,0 +1,6 @@ + +public class InjarOneMain { + public static void main(String[] args) { + System.out.println("in InjarOneMain.main(..)"); + } +} diff --git a/taskdefs/testdata/incTest/injarSrc/one/one.properties b/taskdefs/testdata/incTest/injarSrc/one/one.properties new file mode 100644 index 000000000..2aaac9a85 --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/one/one.properties @@ -0,0 +1 @@ +hel=lo diff --git a/taskdefs/testdata/incTest/injarSrc/one/overview.gif b/taskdefs/testdata/incTest/injarSrc/one/overview.gif Binary files differnew file mode 100644 index 000000000..7b1d6c8d6 --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/one/overview.gif diff --git a/taskdefs/testdata/incTest/injarSrc/one/subdir/examples.xml b/taskdefs/testdata/incTest/injarSrc/one/subdir/examples.xml new file mode 100644 index 000000000..2e6a1715d --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/one/subdir/examples.xml @@ -0,0 +1,2343 @@ +<chapter id="examples" xreflabel="Examples"> + <title>Examples</title> + + <sect1><!-- About this Chapter --> + <title>About this Chapter</title> + + <para>This chapter consists entirely of examples of AspectJ use. + +<!-- ADD THIS IN AGAIN WHEN IT'S TRUE + The + examples have been chosen because they illustrate common AspectJ usage + patterns or techniques. Care has been taken to ensure that they also + exhibit good style, in addition to being merely syntactically and + semantically correct. +--> +</para> + + <para>The examples can be grouped into four categories:</para> + + <simplelist columns="2" type="horiz"> + <member><emphasis role="bold">technique</emphasis></member> + <member>Examples which illustrate how to use one or more features of the + language. </member> + + <member><emphasis role="bold">development</emphasis></member> + <member>Examples of using AspectJ during the development phase of a + project. </member> + + <member><emphasis role="bold">production</emphasis></member> + <member>Examples of using AspectJ to provide functionality in an + application. </member> + + <member><emphasis role="bold">reusable</emphasis></member> + <member>Examples of reuse of aspects and pointcuts.</member> + </simplelist> + + </sect1> + + + <sect1> + <title>Obtaining, Compiling and Running the Examples</title> + + <para>The examples source code is part of AspectJ's documentation + distribution which may be downloaded from <ulink + url="http://aspectj.org/dl">the AspectJ download page</ulink>.</para> + + <para>Compiling most examples should be straightforward. Go the + <filename><replaceable>InstallDir</replaceable>/examples</filename> + directory, and look for a <filename>.lst</filename> file in one of the + example subdirectories. Use the <literal>-arglist</literal> option to + <literal>ajc</literal> to compile the example. For instance, to compile + the telecom example with billing, type </para> + +<programlisting> +ajc -argfile telecom/billing.lst +</programlisting> + + <para>To run the examples, your classpath must include the AspectJ run-time + Java archive (<literal>aspectjrt.jar</literal>). You may either set + the <literal>CLASSPATH</literal> environment variable or use the + <literal>-classpath</literal> command line option to the Java + interpreter:</para> + +<programlisting> +(In Unix use a : in the CLASSPATH) +java -classpath ".:<replaceable>InstallDir</replaceable>/lib/aspectjrt.jar" telecom.billingSimulation +</programlisting> + +<programlisting> +(In Windows use a ; in the CLASSPATH) +java -classpath ".;<replaceable>InstallDir</replaceable>/lib/aspectjrt.jar" telecom.billingSimulation +</programlisting> + + </sect1> + + +<!-- ============================================================ --> +<!-- ============================================================ --> + + + <sect1> + <title>Basic Techniques</title> + + <para>This section presents two basic techniques of using AspectJ, one each + from the two fundamental ways of capturing crosscutting concerns: with + dynamic join points and advice, and with static introduction. Advice + changes an application's behavior. Introduction changes both an + application's behavior and its structure. </para> + + <para>The first example, <xref endterm="sec:JoinPointsAndtjp:title" + linkend="sec:JoinPointsAndtjp"/>, is about gathering and using + information about the join point that has triggered some advice. The + second example, <xref endterm="sec:RolesAndViews:title" + linkend="sec:RolesAndViews"/>, concerns changing an existing class + hierarchy. </para> + +<!-- ======================================== --> + + <sect2 id="sec:JoinPointsAndtjp"><!-- Join Points and thisJoinPoint --> + <title>Join Points and <literal>thisJoinPoint</literal></title> + <titleabbrev id="sec:JoinPointsAndtjp:title">Join Points and + <literal>thisJoinPoint</literal></titleabbrev> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/tjp</filename>.)</para> + + <para>A join point is some point in the + execution of a program together with a view into the execution context + when that point occurs. Join points are picked out by pointcuts. When a + join point is reached, before, after or around advice on that join + point may be run. </para> + + <para>When dealing with pointcuts that pick out join points of specific + method calls, field gets, or the like, the advice will know exactly what + kind of join point it is executing under. It might even have access to + context given by its pointcut. Here, for example, since the only join + points reached will be calls of a certain method, we can get the target + and one of the args of the method directly. + </para> + +<programlisting><![CDATA[ +before(Point p, int x): target(p) + && args(x) + && call(void setX(int)) { + if (!p.assertX(x)) { + System.out.println("Illegal value for x"); return; + } +} +]]></programlisting> + + <para>But sometimes the join point is not so clear. For + instance, suppose a complex application is being debugged, and one + would like to know when any method in some class is being executed. + Then, the pointcut </para> + +<programlisting><![CDATA[ +pointcut execsInProblemClass(): within(ProblemClass) + && execution(* *(..)); +]]></programlisting> + + <para>will select all join points where a method defined within the class + <classname>ProblemClass</classname> is being executed. But advice + executes when a particular join point is matched, and so the question, + "Which join point was matched?" naturally arises.</para> + + <para>Information about the join point that was matched is available to + advice through the special variable <varname>thisJoinPoint</varname>, + of type <ulink + url="../api/org/aspectj/lang/JoinPoint.html"><classname>org.aspectj.lang.JoinPoint</classname></ulink>. This + class provides methods that return</para> + + <itemizedlist spacing="compact"> + <listitem>the kind of join point that was matched + </listitem> + <listitem>the source location of the current join point + </listitem> + <listitem>normal, short and long string representations of the + current join point</listitem> + <listitem>the actual argument(s) to the method or field selected + by the current join point </listitem> + <listitem>the signature of the method or field selected by the + current join point</listitem> + <listitem>the target object</listitem> + <listitem>the currently executing object</listitem> + <listitem>a reference to the static portion of the object + representing the current join point. This is also available through + the special variable <varname>thisJoinPointStaticPart</varname>.</listitem> + + </itemizedlist> + + <sect3> + <title>The <classname>Demo</classname> class</title> + + <para>The class <classname>tjp.Demo</classname> in + <filename>tjp/Demo.java</filename> defines two methods + <literal>foo</literal> and <literal>bar</literal> with different + parameter lists and return types. Both are called, with suitable + arguments, by <classname>Demo</classname>'s <function>go</function> + method which was invoked from within its <function>main</function> + method. </para> + +<programlisting><![CDATA[ +public class Demo { + + static Demo d; + + public static void main(String[] args){ + new Demo().go(); + } + + void go(){ + d = new Demo(); + d.foo(1,d); + System.out.println(d.bar(new Integer(3))); + } + + void foo(int i, Object o){ + System.out.println("Demo.foo(" + i + ", " + o + ")\n"); + } + + + String bar (Integer j){ + System.out.println("Demo.bar(" + j + ")\n"); + return "Demo.bar(" + j + ")"; + } + +} +]]></programlisting> + + </sect3> + + <sect3> + <title>The Aspect <literal>GetInfo</literal></title> + + <para>This aspect uses around advice to intercept the execution of + methods <literal>foo</literal> and <literal>bar</literal> in + <classname>Demo</classname>, and prints out information garnered from + <literal>thisJoinPoint</literal> to the console. </para> + + <sect4> + <title>Defining the scope of a pointcut</title> + + <para>The pointcut <function>goCut</function> is defined as + <literal><![CDATA[cflow(this(Demo)) && execution(void + go())]]></literal> so that only executions made in the control + flow of <literal>Demo.go</literal> are intercepted. The control + flow from the method <literal>go</literal> includes the execution of + <literal>go</literal> itself, so the definition of the around + advice includes <literal>!execution(* go())</literal> to exclude it + from the set of executions advised. </para> + </sect4> + + <sect4> + <title>Printing the class and method name</title> + + <para>The name of the method and that method's defining class are + available as parts of the <ulink + url="../api/org/aspectj/lang/Signature.html">Signature</ulink>, + found using the method <literal>getSignature</literal> of either + <literal>thisJoinPoint</literal> or + <literal>thisJoinPointStaticPart</literal>. </para> + +<programlisting><![CDATA[ +aspect GetInfo { + + static final void println(String s){ System.out.println(s); } + + pointcut goCut(): cflow(this(Demo) && execution(void go())); + + pointcut demoExecs(): within(Demo) && execution(* *(..)); + + Object around(): demoExecs() && !execution(* go()) && goCut() { + println("Intercepted message: " + + thisJoinPointStaticPart.getSignature().getName()); + println("in class: " + + thisJoinPointStaticPart.getSignature().getDeclaringType().getName()); + printParameters(thisJoinPoint); + println("Running original method: \n" ); + Object result = proceed(); + println(" result: " + result ); + return result; + } + + static private void printParameters(JoinPoint jp) { + println("Arguments: " ); + Object[] args = jp.getArgs(); + String[] names = ((CodeSignature)jp.getSignature()).getParameterNames(); + Class[] types = ((CodeSignature)jp.getSignature()).getParameterTypes(); + for (int i = 0; i < args.length; i++) { + println(" " + i + ". " + names[i] + + " : " + types[i].getName() + + " = " + args[i]); + } + } +} +]]></programlisting> + </sect4> + + <sect4> + <title>Printing the parameters</title> + + <para> + The static portions of the parameter details, the name and + types of the parameters, can be accessed through the <ulink + url="../api/org/aspectj/lang/reflect/CodeSignature.html"><literal>CodeSignature</literal></ulink> + associated with the join point. All execution join points have code + signatures, so the cast to <literal>CodeSignature</literal> + cannot fail. </para> + + <para> + The dynamic portions of the parameter details, the actual + values of the parameters, are accessed directly from the execution + join point object. </para> + </sect4> + </sect3> + </sect2> + + <sect2 id="sec:RolesAndViews"> + <title>Roles and Views Using Introduction</title> + <titleabbrev id="sec:RolesAndViews:title">Roles and Views Using + Introduction</titleabbrev> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/introduction</filename>.)</para> + + <para>Like advice, pieces of introduction are members of an aspect. They + define new members that act as if they were defined on another + class. Unlike advice, introduction affects not only the behavior of the + application, but also the structural relationship between an + application's classes. </para> + + <para>This is crucial: Affecting the class structure of an application at + makes these modifications available to other components of the + application.</para> + + <para>Introduction modifies a class by adding or changing</para> + <itemizedlist spacing="compact"> + <listitem>member fields</listitem> + <listitem>member methods</listitem> + <listitem>nested classes</listitem> + </itemizedlist> + + <para>and by making the class</para> + + <itemizedlist spacing="compact"> + <listitem>implement interfaces</listitem> + <listitem>extend classes</listitem> + </itemizedlist> + + <para> + This example provides three illustrations of the use of introduction to + encapsulate roles or views of a class. The class we will be introducing + into, <classname>Point</classname>, is a simple class with rectangular + and polar coordinates. Our introduction will make the class + <classname>Point</classname>, in turn, cloneable, hashable, and + comparable. These facilities are provided by introduction forms without + having to modify the class <classname>Point</classname>. + </para> + + <sect3> + <title>The class <classname>Point</classname></title> + + <para>The class <classname>Point</classname> defines geometric points + whose interface includes polar and rectangular coordinates, plus some + simple operations to relocate points. <classname>Point</classname>'s + implementation has attributes for both its polar and rectangular + coordinates, plus flags to indicate which currently reflect the + position of the point. Some operations cause the polar coordinates to + be updated from the rectangular, and some have the opposite effect. + This implementation, which is in intended to give the minimum number + of conversions between coordinate systems, has the property that not + all the attributes stored in a <classname>Point</classname> object + are necessary to give a canonical representation such as might be + used for storing, comparing, cloning or making hash codes from + points. Thus the aspects, though simple, are not totally trivial. + </para> + + <para> + The diagram below gives an overview of the aspects and their + interaction with the class <classname>Point</classname>.</para> + + <para> + <inlinemediaobject> + <imageobject> + <imagedata fileref="aspects.gif"/> + </imageobject> + </inlinemediaobject> + </para> + <para></para> + + </sect3> + + <sect3> + <title>Making <classname>Point</classname>s Cloneable — The Aspect + <classname>CloneablePoint</classname></title> + + <para>This first example demonstrates the introduction of a interface + (<classname>Cloneable</classname>) and a method + (<function>clone</function>) into the class + <classname>Point</classname>. In Java, all objects inherit the method + <literal>clone</literal> from the class + <classname>Object</classname>, but an object is not cloneable unless + its class also implements the interface + <classname>Cloneable</classname>. In addition, classes frequently + have requirements over and above the simple bit-for-bit copying that + <literal>Object.clone</literal> does. In our case, we want to update + a <classname>Point</classname>'s coordinate systems before we + actually clone the <classname>Point</classname>. So we have to + override <literal>Object.clone</literal> with a new method that does + what we want. </para> + + <para>The <classname>CloneablePoint</classname> aspect uses the + <literal>declare parents</literal> form to introduce the interface + <classname>Cloneable</classname> into the class + <classname>Point</classname>. It then defines a method, + <literal>Point.clone</literal>, which overrides the method + <function>clone</function> that was inherited from + <classname>Object</classname>. <function>Point.clone</function> + updates the <classname>Point</classname>'s coordinate systems before + invoking its superclass' <function>clone</function> method.</para> + + <programlisting><![CDATA[ +public aspect CloneablePoint { + + declare parents: Point implements Cloneable; + + public Object Point.clone() throws CloneNotSupportedException { + // we choose to bring all fields up to date before cloning. + makeRectangular(); + makePolar(); + return super.clone(); + } + + public static void main(String[] args){ + Point p1 = new Point(); + Point p2 = null; + + p1.setPolar(Math.PI, 1.0); + try { + p2 = (Point)p1.clone(); + } catch (CloneNotSupportedException e) {} + System.out.println("p1 =" + p1 ); + System.out.println("p2 =" + p2 ); + + p1.rotate(Math.PI / -2); + System.out.println("p1 =" + p1 ); + System.out.println("p2 =" + p2 ); + } +} +]]></programlisting> + + <para>Note that since aspects define types just as classes define + types, we can define a <function>main</function> method that is + invocable from the command line to use as a test method.</para> + </sect3> + + <sect3> + <title>Making <classname>Point</classname>s Comparable — The + Aspect <classname>ComparablePoint</classname></title> + + <para>This second example introduces another interface and + method into the class <classname>Point</classname>.</para> + + <para>The interface <classname>Comparable</classname> defines the + single method <literal>compareTo</literal> which can be use to define + a natural ordering relation among the objects of a class that + implement it. </para> + + <para>The aspect <classname>ComparablePoint</classname> introduces + implements <classname>Comparable</classname> into + <classname>Point</classname> along with a + <literal>compareTo</literal> method that can be used to compare + <classname>Point</classname>s. A <classname>Point</classname> + <literal>p1</literal> is said to be less than + another <classname>Point</classname><literal> p2</literal> if + <literal>p1</literal> is closer to the origin. </para> + + <programlisting><![CDATA[ +public aspect ComparablePoint { + + declare parents: Point implements Comparable; + + public int Point.compareTo(Object o) { + return (int) (this.getRho() - ((Point)o).getRho()); + } + + public static void main(String[] args){ + Point p1 = new Point(); + Point p2 = new Point(); + + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.setRectangular(2,5); + p2.setRectangular(2,5); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p2.setRectangular(3,6); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.setPolar(Math.PI, 4); + p2.setPolar(Math.PI, 4); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.rotate(Math.PI / 4.0); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.offset(1,1); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + } +}]]></programlisting> + </sect3> + + <sect3> + <title>Making <classname>Point</classname>s Hashable — The Aspect + <classname>HashablePoint</classname></title> + + <para>The third aspect overrides two previously defined methods to + give to <classname>Point</classname> the hashing behavior we + want.</para> + + <para>The method <literal>Object.hashCode</literal> returns an unique + integer, suitable for use as a hash table key. Different + implementations are allowed return different integers, but must + return distinct integers for distinct objects, and the same integer + for objects that test equal. But since the default implementation + of <literal>Object.equal</literal> returns <literal>true</literal> + only when two objects are identical, we need to redefine both + <function>equals</function> and <function>hashCode</function> to work + correctly with objects of type <classname>Point</classname>. For + example, we want two <classname>Point</classname> objects to test + equal when they have the same <literal>x</literal> and + <literal>y</literal> values, or the same <literal>rho</literal> and + <literal>theta</literal> values, not just when they refer to the same + object. We do this by overriding the methods + <literal>equals</literal> and <literal>hashCode</literal> in the + class <classname>Point</classname>. </para> + + <para>The class <classname>HashablePoint</classname> introduces the + methods <literal>hashCode</literal> and <literal>equals</literal> + into the class <classname>Point</classname>. These methods use + <classname>Point</classname>'s rectangular coordinates to generate a + hash code and to test for equality. The <literal>x</literal> and + <literal>y</literal> coordinates are obtained using the appropriate + get methods, which ensure the rectangular coordinates are up-to-date + before returning their values. </para> + + <programlisting><![CDATA[ +public aspect HashablePoint { + + public int Point.hashCode() { + return (int) (getX() + getY() % Integer.MAX_VALUE); + } + + public boolean Point.equals(Object o) { + if (o == this) { return true; } + if (!(o instanceof Point)) { return false; } + Point other = (Point)o; + return (getX() == other.getX()) && (getY() == other.getY()); + } + + public static void main(String[] args) { + Hashtable h = new Hashtable(); + Point p1 = new Point(); + + p1.setRectangular(10, 10); + Point p2 = new Point(); + + p2.setRectangular(10, 10); + + System.out.println("p1 = " + p1); + System.out.println("p2 = " + p2); + System.out.println("p1.hashCode() = " + p1.hashCode()); + System.out.println("p2.hashCode() = " + p2.hashCode()); + + h.put(p1, "P1"); + System.out.println("Got: " + h.get(p2)); + } +} +]]></programlisting> + + <para> Again, we supply a <literal>main</literal> method in the aspect + for testing. + </para> + + </sect3> + + </sect2> + + </sect1> + +<!-- ============================================================ --> +<!-- ============================================================ --> + + <sect1> + <title>Development Aspects</title> + + <sect2> + <title>Tracing Aspects</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/tracing</filename>.) + </para> + + <sect3> + <title>Overview</title> + + <para> + Writing a class that provides tracing functionality is easy: a couple + of functions, a boolean flag for turning tracing on and off, a choice + for an output stream, maybe some code for formatting the output---these + are all elements that <classname>Trace</classname> classes have been + known to have. <classname>Trace</classname> classes may be highly + sophisticated, too, if the task of tracing the execution of a program + demands so. + </para> + + <para> + But developing the support for tracing is just one part of the effort + of inserting tracing into a program, and, most likely, not the biggest + part. The other part of the effort is calling the tracing functions at + appropriate times. In large systems, this interaction with the tracing + support can be overwhelming. Plus, tracing is one of those things that + slows the system down, so these calls should often be pulled out of the + system before the product is shipped. For these reasons, it is not + unusual for developers to write ad-hoc scripting programs that rewrite + the source code by inserting/deleting trace calls before and after the + method bodies. + </para> + + <para> + AspectJ can be used for some of these tracing concerns in a less ad-hoc + way. Tracing can be seen as a concern that crosscuts the entire system + and as such is amenable to encapsulation in an aspect. In addition, it + is fairly independent of what the system is doing. Therefore tracing is + one of those kind of system aspects that can potentially be plugged in + and unplugged without any side-effects in the basic functionality of + the system. + </para> + </sect3> + + <sect3> + <title>An Example Application</title> + + <para> + Throughout this example we will use a simple application that contains + only four classes. The application is about shapes. The + <classname>TwoDShape</classname> class is the root of the shape + hierarchy: + </para> + +<programlisting><![CDATA[ +public abstract class TwoDShape { + protected double x, y; + protected TwoDShape(double x, double y) { + this.x = x; this.y = y; + } + public double getX() { return x; } + public double getY() { return y; } + public double distance(TwoDShape s) { + double dx = Math.abs(s.getX() - x); + double dy = Math.abs(s.getY() - y); + return Math.sqrt(dx*dx + dy*dy); + } + public abstract double perimeter(); + public abstract double area(); + public String toString() { + return (" @ (" + String.valueOf(x) + ", " + String.valueOf(y) + ") "); + } +} +]]></programlisting> + + <para> + <classname>TwoDShape</classname> has two subclasses, + <classname>Circle</classname> and <classname>Square</classname>: + </para> + +<programlisting><![CDATA[ +public class Circle extends TwoDShape { + protected double r; + public Circle(double x, double y, double r) { + super(x, y); this.r = r; + } + public Circle(double x, double y) { this( x, y, 1.0); } + public Circle(double r) { this(0.0, 0.0, r); } + public Circle() { this(0.0, 0.0, 1.0); } + public double perimeter() { + return 2 * Math.PI * r; + } + public double area() { + return Math.PI * r*r; + } + public String toString() { + return ("Circle radius = " + String.valueOf(r) + super.toString()); + } +} +]]></programlisting> + +<programlisting><![CDATA[ +public class Square extends TwoDShape { + protected double s; // side + public Square(double x, double y, double s) { + super(x, y); this.s = s; + } + public Square(double x, double y) { this( x, y, 1.0); } + public Square(double s) { this(0.0, 0.0, s); } + public Square() { this(0.0, 0.0, 1.0); } + public double perimeter() { + return 4 * s; + } + public double area() { + return s*s; + } + public String toString() { + return ("Square side = " + String.valueOf(s) + super.toString()); + } +} +]]></programlisting> + + <para> + To run this application, compile the classes. You can do it with or + without ajc, the AspectJ compiler. If you've installed AspectJ, go to + the directory + <filename><replaceable>InstallDir</replaceable>/examples</filename> and + type: + </para> + +<programlisting> +ajc -argfile tracing/notrace.lst +</programlisting> + + <para>To run the program, type</para> + +<programlisting> +java tracing.ExampleMain +</programlisting> + + <para>(we don't need anything special on the classpath since this is pure + Java code). You should see the following output:</para> + +<programlisting><![CDATA[ +c1.perimeter() = 12.566370614359172 +c1.area() = 12.566370614359172 +s1.perimeter() = 4.0 +s1.area() = 1.0 +c2.distance(c1) = 4.242640687119285 +s1.distance(c1) = 2.23606797749979 +s1.toString(): Square side = 1.0 @ (1.0, 2.0) +]]></programlisting> + + </sect3> + <sect3> + <title>Tracing—Version 1</title> + + <para> + In a first attempt to insert tracing in this application, we will start + by writing a <classname>Trace</classname> class that is exactly what we + would write if we didn't have aspects. The implementation is in + <filename>version1/Trace.java</filename>. Its public interface is: + </para> + +<programlisting><![CDATA[ +public class Trace { + public static int TRACELEVEL = 0; + public static void initStream(PrintStream s) {...} + public static void traceEntry(String str) {...} + public static void traceExit(String str) {...} +} +]]></programlisting> + + <para> + If we didn't have AspectJ, we would have to insert calls to + <literal>traceEntry</literal> and <literal>traceExit</literal> in all + methods and constructors we wanted to trace, and to initialize + <literal>TRACELEVEL</literal> and the stream. If we wanted to trace all + the methods and constructors in our example, that would amount to + around 40 calls, and we would hope we had not forgotten any method. But + we can do that more consistently and reliably with the following + aspect (found in <filename>version1/TraceMyClasses.java</filename>): + </para> + +<programlisting><![CDATA[ +aspect TraceMyClasses { + pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square); + pointcut myConstructor(): myClass() && execution(new(..)); + pointcut myMethod(): myClass() && execution(* *(..)); + + before (): myConstructor() { + Trace.traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myConstructor() { + Trace.traceExit("" + thisJoinPointStaticPart.getSignature()); + } + + before (): myMethod() { + Trace.traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myMethod() { + Trace.traceExit("" + thisJoinPointStaticPart.getSignature()); + } +}]]></programlisting> + + <para> + This aspect performs the tracing calls at appropriate times. According + to this aspect, tracing is performed at the entrance and exit of every + method and constructor defined within the shape hierarchy. + </para> + + <para> + What is printed at before and after each of the traced + join points is the signature of the method executing. Since the + signature is static information, we can get it through + <literal>thisJoinPointStaticPart</literal>. + </para> + + <para> + To run this version of tracing, go to the directory + <filename><replaceable>InstallDir</replaceable>/examples</filename> and + type: + </para> + +<programlisting><![CDATA[ + ajc -argfile tracing/tracev1.lst +]]></programlisting> + + <para> + Running the main method of + <classname>tracing.version1.TraceMyClasses</classname> should produce + the output: + </para> + +<programlisting><![CDATA[ + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.Circle(double) + <-- tracing.Circle(double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Square(double, double, double) + <-- tracing.Square(double, double, double) + --> tracing.Square(double, double) + <-- tracing.Square(double, double) + --> double tracing.Circle.perimeter() + <-- double tracing.Circle.perimeter() +c1.perimeter() = 12.566370614359172 + --> double tracing.Circle.area() + <-- double tracing.Circle.area() +c1.area() = 12.566370614359172 + --> double tracing.Square.perimeter() + <-- double tracing.Square.perimeter() +s1.perimeter() = 4.0 + --> double tracing.Square.area() + <-- double tracing.Square.area() +s1.area() = 1.0 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +c2.distance(c1) = 4.242640687119285 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +s1.distance(c1) = 2.23606797749979 + --> String tracing.Square.toString() + --> String tracing.TwoDShape.toString() + <-- String tracing.TwoDShape.toString() + <-- String tracing.Square.toString() +s1.toString(): Square side = 1.0 @ (1.0, 2.0) +]]></programlisting> + + <para> + When <filename>TraceMyClasses.java</filename> is not provided to + <command>ajc</command>, the aspect does not have any affect on the + system and the tracing is unplugged. + </para> + </sect3> + + <sect3> + <title>Tracing—Version 2</title> + + <para> + Another way to accomplish the same thing would be to write a reusable + tracing aspect that can be used not only for these application classes, + but for any class. One way to do this is to merge the tracing + functionality of <literal>Trace—version1</literal> with the + crosscutting support of + <literal>TraceMyClasses—version1</literal>. We end up with a + <literal>Trace</literal> aspect (found in + <filename>version2/Trace.java</filename>) with the following public + interface + </para> + +<programlisting><![CDATA[ +abstract aspect Trace { + + public static int TRACELEVEL = 2; + public static void initStream(PrintStream s) {...} + protected static void traceEntry(String str) {...} + protected static void traceExit(String str) {...} + abstract pointcut myClass(); +} +]]></programlisting> + + <para> + In order to use it, we need to define our own subclass that knows about + our application classes, in <filename>version2/TraceMyClasses.java</filename>: + </para> + +<programlisting><![CDATA[ +public aspect TraceMyClasses extends Trace { + pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square); + + public static void main(String[] args) { + Trace.TRACELEVEL = 2; + Trace.initStream(System.err); + ExampleMain.main(args); + } +} +]]></programlisting> + + <para> + Notice that we've simply made the pointcut <literal>classes</literal>, + that was an abstract pointcut in the super-aspect, concrete. To run + this version of tracing, go to the directory + <filename>examples</filename> and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile tracing/tracev2.lst +]]></programlisting> + + <para> + The file tracev2.lst lists the application classes as well as this + version of the files Trace.java and TraceMyClasses.java. Running the + main method of <classname>tracing.version2.TraceMyClasses</classname> + should output exactly the same trace information as that from version + 1. + </para> + + <para> + The entire implementation of the new <classname>Trace</classname> class + is: + </para> + +<programlisting><![CDATA[ +abstract aspect Trace { + + // implementation part + + public static int TRACELEVEL = 2; + protected static PrintStream stream = System.err; + protected static int callDepth = 0; + + public static void initStream(PrintStream s) { + stream = s; + } + protected static void traceEntry(String str) { + if (TRACELEVEL == 0) return; + if (TRACELEVEL == 2) callDepth++; + printEntering(str); + } + protected static void traceExit(String str) { + if (TRACELEVEL == 0) return; + printExiting(str); + if (TRACELEVEL == 2) callDepth--; + } + private static void printEntering(String str) { + printIndent(); + stream.println("--> " + str); + } + private static void printExiting(String str) { + printIndent(); + stream.println("<-- " + str); + } + private static void printIndent() { + for (int i = 0; i < callDepth; i++) + stream.print(" "); + } + + // protocol part + + abstract pointcut myClass(); + + pointcut myConstructor(): myClass() && execution(new(..)); + pointcut myMethod(): myClass() && execution(* *(..)); + + before(): myConstructor() { + traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myConstructor() { + traceExit("" + thisJoinPointStaticPart.getSignature()); + } + + before(): myMethod() { + traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myMethod() { + traceExit("" + thisJoinPointStaticPart.getSignature()); + } +} +]]></programlisting> + + <para> + This version differs from version 1 in several subtle ways. The first + thing to notice is that this <classname>Trace</classname> class merges + the functional part of tracing with the crosscutting of the tracing + calls. That is, in version 1, there was a sharp separation between the + tracing support (the class <classname>Trace</classname>) and the + crosscutting usage of it (by the class + <classname>TraceMyClasses</classname>). In this version those two + things are merged. That's why the description of this class explicitly + says that "Trace messages are printed before and after constructors and + methods are," which is what we wanted in the first place. That is, the + placement of the calls, in this version, is established by the aspect + class itself, leaving less opportunity for misplacing calls.</para> + + <para> + A consequence of this is that there is no need for providing traceEntry + and traceExit as public operations of this class. You can see that they + were classified as protected. They are supposed to be internal + implementation details of the advice. + </para> + + <para> + The key piece of this aspect is the abstract pointcut classes that + serves as the base for the definition of the pointcuts constructors and + methods. Even though <classname>classes</classname> is abstract, and + therefore no concrete classes are mentioned, we can put advice on it, + as well as on the pointcuts that are based on it. The idea is "we don't + know exactly what the pointcut will be, but when we do, here's what we + want to do with it." In some ways, abstract pointcuts are similar to + abstract methods. Abstract methods don't provide the implementation, + but you know that the concrete subclasses will, so you can invoke those + methods. + </para> + </sect3> + </sect2> + </sect1> + +<!-- ============================================================ --> +<!-- ============================================================ --> + + <sect1> + <title>Production Aspects</title> + + <!-- ==================== --> + + <sect2><!-- A Bean Aspect --> + <title>A Bean Aspect</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/bean</filename>.) + </para> + + <para> + This example examines an aspect that makes Point objects into a Java beans + with bound properties. </para> + + <sect3> + <title>Introduction</title> + <para> + Java beans are reusable software components that can be visually + manipulated in a builder tool. The requirements for an object to be a + bean are few. Beans must define a no-argument constructor and must be + either <classname>Serializable</classname> or + <classname>Externalizable</classname>. Any properties of the object + that are to be treated as bean properties should be indicated by the + presence of appropriate <literal>get</literal> and + <literal>set</literal> methods whose names are + <literal>get</literal><emphasis>property</emphasis> and + <literal>set </literal><emphasis>property</emphasis> + where <emphasis>property</emphasis> is the name of a field in the bean + class. Some bean properties, known as bound properties, fire events + whenever their values change so that any registered listeners (such as, + other beans) will be informed of those changes. Making a bound property + involves keeping a list of registered listeners, and creating and + dispatching event objects in methods that change the property values, + such as set<emphasis>property</emphasis> methods.</para> + + <para> + <classname>Point</classname> is a simple class representing points with + rectangular coordinates. <classname>Point</classname> does not know + anything about being a bean: there are set methods for + <literal>x</literal> and <literal>y</literal> but they do not fire + events, and the class is not serializable. Bound is an aspect that + makes <classname>Point</classname> a serializable class and makes its + <literal>get</literal> and <literal>set</literal> methods support the + bound property protocol. + </para> + </sect3> + + <sect3> + <title>The Class <classname>Point</classname></title> + + <para> + The class <classname>Point</classname> is a very simple class with + trivial getters and setters, and a simple vector offset method. + </para> + + <programlisting><![CDATA[ +class Point { + + protected int x = 0; + protected int y = 0; + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public void setRectangular(int newX, int newY) { + setX(newX); + setY(newY); + } + + public void setX(int newX) { + x = newX; + } + + public void setY(int newY) { + y = newY; + } + + public void offset(int deltaX, int deltaY) { + setRectangular(x + deltaX, y + deltaY); + } + + public String toString() { + return "(" + getX() + ", " + getY() + ")" ; + } +}]]></programlisting> + + </sect3> + + <sect3> + <title>The Aspect <classname>BoundPoint</classname></title> + + <para> + The aspect <classname>BoundPoint</classname> adds "beanness" to + <classname>Point</classname> objects. The first thing it does is + privately introduce a reference to an instance of + <classname>PropertyChangeSupport</classname> into all + <classname>Point</classname> objects. The property change + support object must be constructed with a reference to the bean for + which it is providing support, so it is initialized by passing it this, + an instance of <classname>Point</classname>. The support field is + privately introduced, so only the code in the aspect can refer to it. + </para> + + <para> + Methods for registering and managing listeners for property change + events are introduced into <classname>Point</classname> by the + introductions. These methods delegate the work to the + property change support object. + </para> + + <para> + The introduction also makes <classname>Point</classname> implement the + <classname>Serializable</classname> interface. Implementing + <classname>Serializable</classname> does not require any methods to be + implemented. Serialization for <classname>Point</classname> objects is + provided by the default serialization method. + </para> + + <para> + The pointcut <function>setters</function> names the + <literal>set</literal> methods: reception by a + <classname>Point</classname> object of any method whose name begins + with '<literal>set</literal>' and takes one parameter. The around + advice on <literal>setters()</literal> stores the values + of the <literal>X</literal> and <literal>Y</literal> properties, calls + the original <literal>set</literal> method and then fires the + appropriate property change event according to which set method was + called. Note that the call to the method proceed needs to pass along + the <literal>Point p</literal>. The rule of thumb is that context that + an around advice exposes must be passed forward to continue. + </para> + +<programlisting><![CDATA[ +aspect BoundPoint { + private PropertyChangeSupport Point.support = new PropertyChangeSupport(this); + + public void Point.addPropertyChangeListener(PropertyChangeListener listener){ + support.addPropertyChangeListener(listener); + } + + public void Point.addPropertyChangeListener(String propertyName, + PropertyChangeListener listener){ + + support.addPropertyChangeListener(propertyName, listener); + } + + public void Point.removePropertyChangeListener(String propertyName, + PropertyChangeListener listener) { + support.removePropertyChangeListener(propertyName, listener); + } + + public void Point.removePropertyChangeListener(PropertyChangeListener listener) { + support.removePropertyChangeListener(listener); + } + + public void Point.hasListeners(String propertyName) { + support.hasListeners(propertyName); + } + + declare parents: Point implements Serializable; + + pointcut setter(Point p): call(void Point.set*(*)) && target(p); + + void around(Point p): setter(p) { + String propertyName = + thisJoinPointStaticPart.getSignature().getName().substring("set".length()); + int oldX = p.getX(); + int oldY = p.getY(); + proceed(p); + if (propertyName.equals("X")){ + firePropertyChange(p, propertyName, oldX, p.getX()); + } else { + firePropertyChange(p, propertyName, oldY, p.getY()); + } + } + + void firePropertyChange(Point p, + String property, + double oldval, + double newval) { + p.support.firePropertyChange(property, + new Double(oldval), + new Double(newval)); + } +} +]]></programlisting> + + </sect3> + + <sect3> + <title>The Test Program</title> + + <para> + The test program registers itself as a property change listener to a + <literal>Point</literal> object that it creates and then performs + simple manipulation of that point: calling its set methods and the + offset method. Then it serializes the point and writes it to a file and + then reads it back. The result of saving and restoring the point is that + a new point is created. + </para> + +<programlisting><![CDATA[ + class Demo implements PropertyChangeListener { + + static final String fileName = "test.tmp"; + + public void propertyChange(PropertyChangeEvent e){ + System.out.println("Property " + e.getPropertyName() + " changed from " + + e.getOldValue() + " to " + e.getNewValue() ); + } + + public static void main(String[] args){ + Point p1 = new Point(); + p1.addPropertyChangeListener(new Demo()); + System.out.println("p1 =" + p1); + p1.setRectangular(5,2); + System.out.println("p1 =" + p1); + p1.setX( 6 ); + p1.setY( 3 ); + System.out.println("p1 =" + p1); + p1.offset(6,4); + System.out.println("p1 =" + p1); + save(p1, fileName); + Point p2 = (Point) restore(fileName); + System.out.println("Had: " + p1); + System.out.println("Got: " + p2); + } + ... + } +]]></programlisting> + + </sect3> + <sect3> + <title>Compiling and Running the Example</title> + <para>To compile and run this example, go to the examples directory and type: + </para> + +<programlisting><![CDATA[ +ajc -argfile bean/files.lst +java bean.Demo +]]></programlisting> + + </sect3> + </sect2> + + <!-- ==================== --> + + <sect2><!-- The Subject/Observer Protocol --> + <title>The Subject/Observer Protocol</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/observer</filename>.) + </para> + + <para> + This demo illustrates how the Subject/Observer design pattern can be + coded with aspects. </para> + + <sect3> + <title>Overview</title> + <para> + The demo consists of the following: A colored label is a renderable + object that has a color that cycles through a set of colors, and a + number that records the number of cycles it has been through. A button + is an action item that records when it is clicked. + </para> + + <para> + With these two kinds of objects, we can build up a Subject/Observer + relationship in which colored labels observe the clicks of buttons; + that is, where colored labels are the observers and buttons are the + subjects. + </para> + + <para> + The demo is designed and implemented using the Subject/Observer design + pattern. The remainder of this example explains the classes and aspects + of this demo, and tells you how to run it. + </para> + </sect3> + + <sect3> + <title>Generic Components</title> + + <para> + The generic parts of the protocol are the interfaces + <classname>Subject</classname> and <classname>Observer</classname>, and + the abstract aspect <classname>SubjectObserverProtocol</classname>. The + <classname>Subject</classname> interface is simple, containing methods + to add, remove, and view <classname>Observer</classname> objects, and a + method for getting data about state changes: + </para> + +<programlisting><![CDATA[ + interface Subject { + void addObserver(Observer obs); + void removeObserver(Observer obs); + Vector getObservers(); + Object getData(); + } +]]></programlisting> + + <para> The <classname>Observer</classname> interface is just as simple, + with methods to set and get <classname>Subject</classname> objects, and + a method to call when the subject gets updated. + </para> + +<programlisting><![CDATA[ + interface Observer { + void setSubject(Subject s); + Subject getSubject(); + void update(); + } +]]></programlisting> + + <para> + The <classname>SubjectObserverProtocol</classname> aspect contains + within it all of the generic parts of the protocol, namely, how to fire + the <classname>Observer</classname> objects' update methods when some + state changes in a subject. + </para> + +<programlisting><![CDATA[ + abstract aspect SubjectObserverProtocol { + + abstract pointcut stateChanges(Subject s); + + after(Subject s): stateChanges(s) { + for (int i = 0; i < s.getObservers().size(); i++) { + ((Observer)s.getObservers().elementAt(i)).update(); + } + } + + private Vector Subject.observers = new Vector(); + public void Subject.addObserver(Observer obs) { + observers.addElement(obs); + obs.setSubject(this); + } + public void Subject.removeObserver(Observer obs) { + observers.removeElement(obs); + obs.setSubject(null); + } + public Vector Subject.getObservers() { return observers; } + + private Subject Observer.subject = null; + public void Observer.setSubject(Subject s) { subject = s; } + public Subject Observer.getSubject() { return subject; } + + } +]]></programlisting> + + <para> + Note that this aspect does three things. It define an abstract pointcut + that extending aspects can override. It defines advice that should run + after the join points of the pointcut. And it introduces state and + behavior onto the <classname>Subject</classname> and + <classname>Observer</classname> interfaces. + </para> + </sect3> + + <sect3> + <title>Application Classes</title> + + <para> <classname>Button</classname> objects extend + <classname>java.awt.Button</classname>, and all they do is make sure + the <literal>void click()</literal> method is called whenever a button + is clicked. + </para> + +<programlisting><![CDATA[ + class Button extends java.awt.Button { + + static final Color defaultBackgroundColor = Color.gray; + static final Color defaultForegroundColor = Color.black; + static final String defaultText = "cycle color"; + + Button(Display display) { + super(); + setLabel(defaultText); + setBackground(defaultBackgroundColor); + setForeground(defaultForegroundColor); + addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Button.this.click(); + } + }); + display.addToFrame(this); + } + + public void click() {} + + } +]]></programlisting> + + <para> + Note that this class knows nothing about being a Subject. + </para> + <para> + ColorLabel objects are labels that support the void colorCycle() + method. Again, they know nothing about being an observer. + </para> + +<programlisting><![CDATA[ + class ColorLabel extends Label { + + ColorLabel(Display display) { + super(); + display.addToFrame(this); + } + + final static Color[] colors = {Color.red, Color.blue, + Color.green, Color.magenta}; + private int colorIndex = 0; + private int cycleCount = 0; + void colorCycle() { + cycleCount++; + colorIndex = (colorIndex + 1) % colors.length; + setBackground(colors[colorIndex]); + setText("" + cycleCount); + } + } +]]></programlisting> + + <para> + Finally, the <classname>SubjectObserverProtocolImpl</classname> + implements the subject/observer protocol, with + <classname>Button</classname> objects as subjects and + <classname>ColorLabel</classname> objects as observers: + </para> + +<programlisting><![CDATA[ +package observer; + +import java.util.Vector; + +aspect SubjectObserverProtocolImpl extends SubjectObserverProtocol { + + declare parents: Button implements Subject; + public Object Button.getData() { return this; } + + declare parents: ColorLabel implements Observer; + public void ColorLabel.update() { + colorCycle(); + } + + pointcut stateChanges(Subject s): + target(s) && + call(void Button.click()); + +}]]></programlisting> + + <para> + It does this by introducing the appropriate interfaces onto the + <classname>Button</classname> and <classname>ColorLabel</classname> + classes, making sure the methods required by the interfaces are + implemented, and providing a definition for the + <literal>stateChanges</literal> pointcut. Now, every time a + <classname>Button</classname> is clicked, all + <classname>ColorLabel</classname> objects observing that button will + <literal>colorCycle</literal>. + </para> + </sect3> + + <sect3> + <title>Compiling and Running</title> + + <para> <classname>Demo</classname> is the top class that starts this + demo. It instantiates a two buttons and three observers and links them + together as subjects and observers. So to run the demo, go to the + <filename>examples</filename> directory and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile observer/files.lst + java observer.Demo +]]></programlisting> + + </sect3> + </sect2> + + <!-- ==================== --> + + <sect2> + <title>A Simple Telecom Simulation</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/telecom</filename>.) + </para> + + <para> + This example illustrates some ways that dependent concerns can be encoded + with aspects. It uses an example system comprising a simple model of + telephone connections to which timing and billing features are added + using aspects, where the billing feature depends upon the timing feature. + </para> + + <sect3> + <title>The Application</title> + + <para> + The example application is a simple simulation of a telephony system in + which customers make, accept, merge and hang-up both local and long + distance calls. The application architecture is in three layers. + </para> + <itemizedlist> + <listitem> + <para> + The basic objects provide basic functionality to simulate + customers, calls and connections (regular calls have one + connection, conference calls have more than one). + </para> + </listitem> + + <listitem> + <para> + The timing feature is concerned with timing the connections and + keeping the total connection time per customer. Aspects are used to + add a timer to each connection and to manage the total time per + customer. + </para> + </listitem> + + <listitem> + <para> + The billing feature is concerned with charging customers for the + calls they make. Aspects are used to calculate a charge per + connection and, upon termination of a connection, to add the charge + to the appropriate customer's bill. The billing aspect builds upon + the timing aspect: it uses a pointcut defined in Timing and it uses + the timers that are associated with connections. + </para> + </listitem> + </itemizedlist> + <para> + The simulation of system has three configurations: basic, timing and + billing. Programs for the three configurations are in classes + <classname>BasicSimulation</classname>, + <classname>TimingSimulation</classname> and + <classname>BillingSimulation</classname>. These share a common + superclass <classname>AbstractSimulation</classname>, which defines the + method run with the simulation itself and the method wait used to + simulate elapsed time. + </para> + </sect3> + + <sect3> + <title>The Basic Objects</title> + + <para> + The telecom simulation comprises the classes + <classname>Customer</classname>, <classname>Call</classname> and the + abstract class <classname>Connection</classname> with its two concrete + subclasses <classname>Local</classname> and + <classname>LongDistance</classname>. Customers have a name and a + numeric area code. They also have methods for managing calls. Simple + calls are made between one customer (the caller) and another (the + receiver), a <classname>Connection</classname> object is used to + connect them. Conference calls between more than two customers will + involve more than one connection. A customer may be involved in many + calls at one time. + <inlinemediaobject> + <imageobject> + <imagedata fileref="telecom.gif"/> + </imageobject> + </inlinemediaobject> + </para> + </sect3> + + <sect3> + <title>The Class <classname>Customer</classname></title> + + <para> + <classname>Customer</classname> has methods <literal>call</literal>, + <literal>pickup</literal>, <literal>hangup</literal> and + <literal>merge</literal> for managing calls. + </para> + +<programlisting><![CDATA[ +public class Customer { + + private String name; + private int areacode; + private Vector calls = new Vector(); + + protected void removeCall(Call c){ + calls.removeElement(c); + } + + protected void addCall(Call c){ + calls.addElement(c); + } + + public Customer(String name, int areacode) { + this.name = name; + this.areacode = areacode; + } + + public String toString() { + return name + "(" + areacode + ")"; + } + + public int getAreacode(){ + return areacode; + } + + public boolean localTo(Customer other){ + return areacode == other.areacode; + } + + public Call call(Customer receiver) { + Call call = new Call(this, receiver); + addCall(call); + return call; + } + + public void pickup(Call call) { + call.pickup(); + addCall(call); + } + + public void hangup(Call call) { + call.hangup(this); + removeCall(call); + } + + public void merge(Call call1, Call call2){ + call1.merge(call2); + removeCall(call2); + } + } +]]></programlisting> + + </sect3> + + <sect3> + <title>The Class <classname>Call</classname></title> + + <para> + Calls are created with a caller and receiver who are customers. If the + caller and receiver have the same area code then the call can be + established with a <classname>Local</classname> connection (see below), + otherwise a <classname>LongDistance</classname> connection is required. + A call comprises a number of connections between customers. Initially + there is only the connection between the caller and receiver but + additional connections can be added if calls are merged to form + conference calls. + </para> + </sect3> + + <sect3> + <title>The Class <classname>Connection</classname></title> + + <para>The class <classname>Connection</classname> models the physical + details of establishing a connection between customers. It does this + with a simple state machine (connections are initially + <literal>PENDING</literal>, then <literal>COMPLETED</literal> and + finally <literal>DROPPED</literal>). Messages are printed to the + console so that the state of connections can be observed. Connection is + an abstract class with two concrete subclasses: + <classname>Local</classname> and <classname>LongDistance</classname>. + </para> + +<programlisting><![CDATA[ + abstract class Connection { + + public static final int PENDING = 0; + public static final int COMPLETE = 1; + public static final int DROPPED = 2; + + Customer caller, receiver; + private int state = PENDING; + + Connection(Customer a, Customer b) { + this.caller = a; + this.receiver = b; + } + + public int getState(){ + return state; + } + + public Customer getCaller() { return caller; } + + public Customer getReceiver() { return receiver; } + + void complete() { + state = COMPLETE; + System.out.println("connection completed"); + } + + void drop() { + state = DROPPED; + System.out.println("connection dropped"); + } + + public boolean connects(Customer c){ + return (caller == c || receiver == c); + } + + } +]]></programlisting> + + </sect3> + + <sect3> + <title>The Class Local</title> + +<programlisting><![CDATA[ + class Local extends Connection { + Local(Customer a, Customer b) { + super(a, b); + System.out.println("[new local connection from " + + a + " to " + b + "]"); + } + } +]]></programlisting> + + </sect3> + + <sect3> + <title>The Class LongDistance</title> + +<programlisting><![CDATA[ + class LongDistance extends Connection { + LongDistance(Customer a, Customer b) { + super(a, b); + System.out.println("[new long distance connection from " + + a + " to " + b + "]"); + } + } +]]></programlisting> + + </sect3> + + <sect3> + <title>Compiling and Running the Basic Simulation</title> + + <para> + The source files for the basic system are listed in the file + <filename>basic.lst</filename>. To build and run the basic system, in a + shell window, type these commands: + </para> + +<programlisting><![CDATA[ +ajc -argfile telecom/basic.lst +java telecom.BasicSimulation +]]></programlisting> + + </sect3> + + <sect3> + <title>Timing</title> + <para> + The <classname>Timing</classname> aspect keeps track of total + connection time for each <classname>Customer</classname> by starting + and stopping a timer associated with each connection. It uses some + helper classes: + </para> + + <sect4> + <title>The Class <classname>Timer</classname></title> + + <para> + A <classname>Timer</classname> object simply records the current time + when it is started and stopped, and returns their difference when + asked for the elapsed time. The aspect + <classname>TimerLog</classname> (below) can be used to cause the + start and stop times to be printed to standard output. + </para> + +<programlisting><![CDATA[ + class Timer { + long startTime, stopTime; + + public void start() { + startTime = System.currentTimeMillis(); + stopTime = startTime; + } + + public void stop() { + stopTime = System.currentTimeMillis(); + } + + public long getTime() { + return stopTime - startTime; + } + } +]]></programlisting> + + </sect4> + </sect3> + + <sect3> + <title>The Aspect <classname>TimerLog</classname></title> + + <para> + The aspect <classname>TimerLog</classname> can be included in a + build to get the timer to announce when it is started and stopped. + </para> + +<programlisting><![CDATA[ +public aspect TimerLog { + + after(Timer t): target(t) && call(* Timer.start()) { + System.err.println("Timer started: " + t.startTime); + } + + after(Timer t): target(t) && call(* Timer.stop()) { + System.err.println("Timer stopped: " + t.stopTime); + } +} +]]></programlisting> + + </sect3> + + <sect3> + <title>The Aspect <classname>Timing</classname></title> + + <para> + The aspect <classname>Timing</classname> introduces attribute + <literal>totalConnectTime</literal> into the class + <classname>Customer</classname> to store the accumulated connection + time per <classname>Customer</classname>. It introduces attribute + timer into <classname>Connection</classname> to associate a timer + with each <classname>Connection</classname>. Two pieces of after + advice ensure that the timer is started when a connection is + completed and and stopped when it is dropped. The pointcut + <literal>endTiming</literal> is defined so that it can be used by the + <classname>Billing</classname> aspect. + </para> + +<programlisting><![CDATA[ +public aspect Timing { + + public long Customer.totalConnectTime = 0; + + public long getTotalConnectTime(Customer cust) { + return cust.totalConnectTime; + } + private Timer Connection.timer = new Timer(); + public Timer getTimer(Connection conn) { return conn.timer; } + + after (Connection c): target(c) && call(void Connection.complete()) { + getTimer(c).start(); + } + + pointcut endTiming(Connection c): target(c) && + call(void Connection.drop()); + + after(Connection c): endTiming(c) { + getTimer(c).stop(); + c.getCaller().totalConnectTime += getTimer(c).getTime(); + c.getReceiver().totalConnectTime += getTimer(c).getTime(); + } +}]]></programlisting> + + </sect3> + + <sect3> + <title>Billing</title> + + <para> + The Billing system adds billing functionality to the telecom + application on top of timing. + </para> + + <sect4> + <title>The Aspect <classname>Billing</classname></title> + + <para> + The aspect <classname>Billing</classname> introduces attribute + <literal>payer</literal> into <classname>Connection</classname> + to indicate who initiated the call and therefore who is + responsible to pay for it. It also introduces method + <literal>callRate</literal> into <classname>Connection</classname> + so that local and long distance calls can be charged + differently. The call charge must be calculated after the timer is + stopped; the after advice on pointcut + <literal>Timing.endTiming</literal> does this and + <classname>Billing</classname> dominates Timing to make + sure that this advice runs after <classname>Timing's</classname> + advice on the same join point. It introduces attribute + <literal>totalCharge</literal> and its associated methods into + <classname>Customer</classname> (to manage the + customer's bill information. + </para> + +<programlisting><![CDATA[ +public aspect Billing dominates Timing { + // domination required to get advice on endtiming in the right order + + public static final long LOCAL_RATE = 3; + public static final long LONG_DISTANCE_RATE = 10; + + + public Customer Connection.payer; + public Customer getPayer(Connection conn) { return conn.payer; } + + after(Customer cust) returning (Connection conn): + args(cust, ..) && call(Connection+.new(..)) { + conn.payer = cust; + } + + public abstract long Connection.callRate(); + + + public long LongDistance.callRate() { return LONG_DISTANCE_RATE; } + public long Local.callRate() { return LOCAL_RATE; } + + + after(Connection conn): Timing.endTiming(conn) { + long time = Timing.aspectOf().getTimer(conn).getTime(); + long rate = conn.callRate(); + long cost = rate * time; + getPayer(conn).addCharge(cost); + } + + + public long Customer.totalCharge = 0; + public long getTotalCharge(Customer cust) { return cust.totalCharge; } + + public void Customer.addCharge(long charge){ + totalCharge += charge; + } +} +]]></programlisting> + + </sect4> + </sect3> + + <sect3> + <title>Accessing the Introduced State</title> + + <para> + Both the aspects <classname>Timing</classname> and + <classname>Billing</classname> contain the definition of operations + that the rest of the system may want to access. For example, when + running the simulation with one or both aspects, we want to find out + how much time each customer spent on the telephone and how big their + bill is. That information is also stored in the classes, but they are + accessed through static methods of the aspects, since the state they + refer to is private to the aspect. + </para> + + <para> + Take a look at the file <filename>TimingSimulation.java</filename>. The + most important method of this class is the method + <filename>report(Customer c)</filename>, which is used in the method + run of the superclass <classname>AbstractSimulation</classname>. This + method is intended to print out the status of the customer, with + respect to the <classname>Timing</classname> feature. + </para> + +<programlisting><![CDATA[ + protected void report(Customer c){ + Timing t = Timing.aspectOf(); + System.out.println(c + " spent " + t.getTotalConnectTime(c)); + } +]]></programlisting> + + </sect3> + + <sect3> + <title>Compiling and Running</title> + + <para> + The files timing.lst and billing.lst contain file lists for the timing + and billing configurations. To build and run the application with only + the timing feature, go to the directory examples and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile telecom/timing.lst + java telecom.TimingSimulation +]]></programlisting> + + <para> + To build and run the application with the timing and billing features, + go to the directory examples and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile telecom/billing.lst + java telecom.BillingSimulation +]]></programlisting> + + </sect3> + + <sect3> + <title>Discussion</title> + + <para> + There are some explicit dependencies between the aspects Billing and + Timing: + <itemizedlist> + <listitem> + <para> + Billing is declared to dominate Timing so that Billing's after + advice runs after that of Timing when they are on the same join + point. + </para> + </listitem> + + <listitem> + <para> + Billing uses the pointcut Timing.endTiming. + </para> + </listitem> + + <listitem> + <para> + Billing needs access to the timer associated with a connection. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> + </sect1> + +<!-- ============================================================ --> +<!-- ============================================================ --> + + <sect1> + <title>Reusable Aspects</title> + + <sect2> + <title>Tracing Aspects Revisited</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/tracing</filename>.) + </para> + + <sect3> + <title>Tracing—Version 3</title> + + <para> + One advantage of not exposing the methods traceEntry and traceExit as + public operations is that we can easily change their interface without + any dramatic consequences in the rest of the code. + </para> + + <para> + Consider, again, the program without AspectJ. Suppose, for example, + that at some point later the requirements for tracing change, stating + that the trace messages should always include the string representation + of the object whose methods are being traced. This can be achieved in + at least two ways. One way is keep the interface of the methods + <literal>traceEntry</literal> and <literal>traceExit</literal> as it + was before, + </para> + +<programlisting><![CDATA[ + public static void traceEntry(String str); + public static void traceExit(String str); +]]></programlisting> + + <para> + In this case, the caller is responsible for ensuring that the string + representation of the object is part of the string given as argument. + So, calls must look like: + </para> + +<programlisting><![CDATA[ + Trace.traceEntry("Square.distance in " + toString()); +]]></programlisting> + + <para> + Another way is to enforce the requirement with a second argument in the + trace operations, e.g. + </para> + +<programlisting><![CDATA[ + public static void traceEntry(String str, Object obj); + public static void traceExit(String str, Object obj); +]]></programlisting> + + <para> + In this case, the caller is still responsible for sending the right + object, but at least there is some guarantees that some object will be + passed. The calls will look like: + </para> + +<programlisting><![CDATA[ + Trace.traceEntry("Square.distance", this); +]]></programlisting> + + <para> + In either case, this change to the requirements of tracing will have + dramatic consequences in the rest of the code -- every call to the + trace operations traceEntry and traceExit must be changed! + </para> + + <para> + Here's another advantage of doing tracing with an aspect. We've already + seen that in version 2 <literal>traceEntry</literal> and + <literal>traceExit</literal> are not publicly exposed. So changing + their interfaces, or the way they are used, has only a small effect + inside the <classname>Trace</classname> class. Here's a partial view at + the implementation of <classname>Trace</classname>, version 3. The + differences with respect to version 2 are stressed in the + comments: + </para> + +<programlisting><![CDATA[ +abstract aspect Trace { + + public static int TRACELEVEL = 0; + protected static PrintStream stream = null; + protected static int callDepth = 0; + + public static void initStream(PrintStream s) { + stream = s; + } + + protected static void traceEntry(String str, Object o) { + if (TRACELEVEL == 0) return; + if (TRACELEVEL == 2) callDepth++; + printEntering(str + ": " + o.toString()); + } + + protected static void traceExit(String str, Object o) { + if (TRACELEVEL == 0) return; + printExiting(str + ": " + o.toString()); + if (TRACELEVEL == 2) callDepth--; + } + + private static void printEntering(String str) { + printIndent(); + stream.println("Entering " + str); + } + + private static void printExiting(String str) { + printIndent(); + stream.println("Exiting " + str); + } + + + private static void printIndent() { + for (int i = 0; i < callDepth; i++) + stream.print(" "); + } + + + abstract pointcut myClass(Object obj); + + pointcut myConstructor(Object obj): myClass(obj) && execution(new(..)); + pointcut myMethod(Object obj): myClass(obj) && + execution(* *(..)) && !execution(String toString()); + + before(Object obj): myConstructor(obj) { + traceEntry("" + thisJoinPointStaticPart.getSignature(), obj); + } + after(Object obj): myConstructor(obj) { + traceExit("" + thisJoinPointStaticPart.getSignature(), obj); + } + + before(Object obj): myMethod(obj) { + traceEntry("" + thisJoinPointStaticPart.getSignature(), obj); + } + after(Object obj): myMethod(obj) { + traceExit("" + thisJoinPointStaticPart.getSignature(), obj); + } +} +]]></programlisting> + + <para> + As you can see, we decided to apply the first design by preserving the + interface of the methods <literal>traceEntry</literal> and + <literal>traceExit</literal>. But it doesn't matter—we could as + easily have applied the second design (the code in the directory + <filename>examples/tracing/version3</filename> has the second design). + The point is that the effects of this change in the tracing + requirements are limited to the <classname>Trace</classname> aspect + class. + </para> + + <para> + One implementation change worth noticing is the specification of the + pointcuts. They now expose the object. To maintain full consistency + with the behavior of version 2, we should have included tracing for + static methods, by defining another pointcut for static methods and + advising it. We leave that as an exercise. + </para> + + <para> + Moreover, we had to exclude the execution join point of the method + <filename>toString</filename> from the <literal>methods</literal> + pointcut. The problem here is that <literal>toString</literal> is being + called from inside the advice. Therefore if we trace it, we will end + up in an infinite recursion of calls. This is a subtle point, and one + that you must be aware when writing advice. If the advice calls back to + the objects, there is always the possibility of recursion. Keep that in + mind! + </para> + + <para> + In fact, esimply excluding the execution join point may not be enough, + if there are calls to other traced methods within it -- in which case, + the restriction should be + </para> + +<programlisting><![CDATA[ +&& !cflow(execution(String toString())) +]]></programlisting> + + <para> + excluding both the execution of toString methods and all join points + under that execution. + </para> + + <para> + In summary, to implement the change in the tracing requirements we had + to make a couple of changes in the implementation of the + <classname>Trace</classname> aspect class, including changing the + specification of the pointcuts. That's only natural. But the + implementation changes were limited to this aspect. Without aspects, we + would have to change the implementation of every application class. + </para> + + <para> + Finally, to run this version of tracing, go to the directory + <filename>examples</filename> and type: + </para> + +<programlisting><![CDATA[ +ajc -argfile tracing/tracev3.lst +]]></programlisting> + + <para> + The file tracev3.lst lists the application classes as well as this + version of the files <filename>Trace.java</filename> and + <filename>TraceMyClasses.java</filename>. To run the program, type + </para> + +<programlisting><![CDATA[ +java tracing.version3.TraceMyClasses +]]></programlisting> + + <para>The output should be:</para> + +<programlisting><![CDATA[ + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.Circle(double) + <-- tracing.Circle(double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Square(double, double, double) + <-- tracing.Square(double, double, double) + --> tracing.Square(double, double) + <-- tracing.Square(double, double) + --> double tracing.Circle.perimeter() + <-- double tracing.Circle.perimeter() +c1.perimeter() = 12.566370614359172 + --> double tracing.Circle.area() + <-- double tracing.Circle.area() +c1.area() = 12.566370614359172 + --> double tracing.Square.perimeter() + <-- double tracing.Square.perimeter() +s1.perimeter() = 4.0 + --> double tracing.Square.area() + <-- double tracing.Square.area() +s1.area() = 1.0 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +c2.distance(c1) = 4.242640687119285 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +s1.distance(c1) = 2.23606797749979 + --> String tracing.Square.toString() + --> String tracing.TwoDShape.toString() + <-- String tracing.TwoDShape.toString() + <-- String tracing.Square.toString() +s1.toString(): Square side = 1.0 @ (1.0, 2.0) +]]></programlisting> + + </sect3> + </sect2> + </sect1> +</chapter> + +<!-- +Local variables: +compile-command: "java sax.SAXCount -v progguide.xml && java com.icl.saxon.StyleSheet -w0 progguide.xml progguide.html.xsl" +fill-column: 79 +sgml-local-ecat-files: "progguide.ced" +sgml-parent-document:("progguide.xml" "book" "chapter") +End: +--> diff --git a/taskdefs/testdata/incTest/injarSrc/two/InjarTwoMain.java b/taskdefs/testdata/incTest/injarSrc/two/InjarTwoMain.java new file mode 100644 index 000000000..c730f31e6 --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/two/InjarTwoMain.java @@ -0,0 +1,6 @@ + +public class InjarTwoMain { + public static void main(String[] args) { + System.out.println("in InjarTwoMain.main(..)"); + } +} diff --git a/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/overview2.gif b/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/overview2.gif Binary files differnew file mode 100644 index 000000000..7b1d6c8d6 --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/overview2.gif diff --git a/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/subdir/twoexamples.xml b/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/subdir/twoexamples.xml new file mode 100644 index 000000000..2e6a1715d --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/subdir/twoexamples.xml @@ -0,0 +1,2343 @@ +<chapter id="examples" xreflabel="Examples"> + <title>Examples</title> + + <sect1><!-- About this Chapter --> + <title>About this Chapter</title> + + <para>This chapter consists entirely of examples of AspectJ use. + +<!-- ADD THIS IN AGAIN WHEN IT'S TRUE + The + examples have been chosen because they illustrate common AspectJ usage + patterns or techniques. Care has been taken to ensure that they also + exhibit good style, in addition to being merely syntactically and + semantically correct. +--> +</para> + + <para>The examples can be grouped into four categories:</para> + + <simplelist columns="2" type="horiz"> + <member><emphasis role="bold">technique</emphasis></member> + <member>Examples which illustrate how to use one or more features of the + language. </member> + + <member><emphasis role="bold">development</emphasis></member> + <member>Examples of using AspectJ during the development phase of a + project. </member> + + <member><emphasis role="bold">production</emphasis></member> + <member>Examples of using AspectJ to provide functionality in an + application. </member> + + <member><emphasis role="bold">reusable</emphasis></member> + <member>Examples of reuse of aspects and pointcuts.</member> + </simplelist> + + </sect1> + + + <sect1> + <title>Obtaining, Compiling and Running the Examples</title> + + <para>The examples source code is part of AspectJ's documentation + distribution which may be downloaded from <ulink + url="http://aspectj.org/dl">the AspectJ download page</ulink>.</para> + + <para>Compiling most examples should be straightforward. Go the + <filename><replaceable>InstallDir</replaceable>/examples</filename> + directory, and look for a <filename>.lst</filename> file in one of the + example subdirectories. Use the <literal>-arglist</literal> option to + <literal>ajc</literal> to compile the example. For instance, to compile + the telecom example with billing, type </para> + +<programlisting> +ajc -argfile telecom/billing.lst +</programlisting> + + <para>To run the examples, your classpath must include the AspectJ run-time + Java archive (<literal>aspectjrt.jar</literal>). You may either set + the <literal>CLASSPATH</literal> environment variable or use the + <literal>-classpath</literal> command line option to the Java + interpreter:</para> + +<programlisting> +(In Unix use a : in the CLASSPATH) +java -classpath ".:<replaceable>InstallDir</replaceable>/lib/aspectjrt.jar" telecom.billingSimulation +</programlisting> + +<programlisting> +(In Windows use a ; in the CLASSPATH) +java -classpath ".;<replaceable>InstallDir</replaceable>/lib/aspectjrt.jar" telecom.billingSimulation +</programlisting> + + </sect1> + + +<!-- ============================================================ --> +<!-- ============================================================ --> + + + <sect1> + <title>Basic Techniques</title> + + <para>This section presents two basic techniques of using AspectJ, one each + from the two fundamental ways of capturing crosscutting concerns: with + dynamic join points and advice, and with static introduction. Advice + changes an application's behavior. Introduction changes both an + application's behavior and its structure. </para> + + <para>The first example, <xref endterm="sec:JoinPointsAndtjp:title" + linkend="sec:JoinPointsAndtjp"/>, is about gathering and using + information about the join point that has triggered some advice. The + second example, <xref endterm="sec:RolesAndViews:title" + linkend="sec:RolesAndViews"/>, concerns changing an existing class + hierarchy. </para> + +<!-- ======================================== --> + + <sect2 id="sec:JoinPointsAndtjp"><!-- Join Points and thisJoinPoint --> + <title>Join Points and <literal>thisJoinPoint</literal></title> + <titleabbrev id="sec:JoinPointsAndtjp:title">Join Points and + <literal>thisJoinPoint</literal></titleabbrev> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/tjp</filename>.)</para> + + <para>A join point is some point in the + execution of a program together with a view into the execution context + when that point occurs. Join points are picked out by pointcuts. When a + join point is reached, before, after or around advice on that join + point may be run. </para> + + <para>When dealing with pointcuts that pick out join points of specific + method calls, field gets, or the like, the advice will know exactly what + kind of join point it is executing under. It might even have access to + context given by its pointcut. Here, for example, since the only join + points reached will be calls of a certain method, we can get the target + and one of the args of the method directly. + </para> + +<programlisting><![CDATA[ +before(Point p, int x): target(p) + && args(x) + && call(void setX(int)) { + if (!p.assertX(x)) { + System.out.println("Illegal value for x"); return; + } +} +]]></programlisting> + + <para>But sometimes the join point is not so clear. For + instance, suppose a complex application is being debugged, and one + would like to know when any method in some class is being executed. + Then, the pointcut </para> + +<programlisting><![CDATA[ +pointcut execsInProblemClass(): within(ProblemClass) + && execution(* *(..)); +]]></programlisting> + + <para>will select all join points where a method defined within the class + <classname>ProblemClass</classname> is being executed. But advice + executes when a particular join point is matched, and so the question, + "Which join point was matched?" naturally arises.</para> + + <para>Information about the join point that was matched is available to + advice through the special variable <varname>thisJoinPoint</varname>, + of type <ulink + url="../api/org/aspectj/lang/JoinPoint.html"><classname>org.aspectj.lang.JoinPoint</classname></ulink>. This + class provides methods that return</para> + + <itemizedlist spacing="compact"> + <listitem>the kind of join point that was matched + </listitem> + <listitem>the source location of the current join point + </listitem> + <listitem>normal, short and long string representations of the + current join point</listitem> + <listitem>the actual argument(s) to the method or field selected + by the current join point </listitem> + <listitem>the signature of the method or field selected by the + current join point</listitem> + <listitem>the target object</listitem> + <listitem>the currently executing object</listitem> + <listitem>a reference to the static portion of the object + representing the current join point. This is also available through + the special variable <varname>thisJoinPointStaticPart</varname>.</listitem> + + </itemizedlist> + + <sect3> + <title>The <classname>Demo</classname> class</title> + + <para>The class <classname>tjp.Demo</classname> in + <filename>tjp/Demo.java</filename> defines two methods + <literal>foo</literal> and <literal>bar</literal> with different + parameter lists and return types. Both are called, with suitable + arguments, by <classname>Demo</classname>'s <function>go</function> + method which was invoked from within its <function>main</function> + method. </para> + +<programlisting><![CDATA[ +public class Demo { + + static Demo d; + + public static void main(String[] args){ + new Demo().go(); + } + + void go(){ + d = new Demo(); + d.foo(1,d); + System.out.println(d.bar(new Integer(3))); + } + + void foo(int i, Object o){ + System.out.println("Demo.foo(" + i + ", " + o + ")\n"); + } + + + String bar (Integer j){ + System.out.println("Demo.bar(" + j + ")\n"); + return "Demo.bar(" + j + ")"; + } + +} +]]></programlisting> + + </sect3> + + <sect3> + <title>The Aspect <literal>GetInfo</literal></title> + + <para>This aspect uses around advice to intercept the execution of + methods <literal>foo</literal> and <literal>bar</literal> in + <classname>Demo</classname>, and prints out information garnered from + <literal>thisJoinPoint</literal> to the console. </para> + + <sect4> + <title>Defining the scope of a pointcut</title> + + <para>The pointcut <function>goCut</function> is defined as + <literal><![CDATA[cflow(this(Demo)) && execution(void + go())]]></literal> so that only executions made in the control + flow of <literal>Demo.go</literal> are intercepted. The control + flow from the method <literal>go</literal> includes the execution of + <literal>go</literal> itself, so the definition of the around + advice includes <literal>!execution(* go())</literal> to exclude it + from the set of executions advised. </para> + </sect4> + + <sect4> + <title>Printing the class and method name</title> + + <para>The name of the method and that method's defining class are + available as parts of the <ulink + url="../api/org/aspectj/lang/Signature.html">Signature</ulink>, + found using the method <literal>getSignature</literal> of either + <literal>thisJoinPoint</literal> or + <literal>thisJoinPointStaticPart</literal>. </para> + +<programlisting><![CDATA[ +aspect GetInfo { + + static final void println(String s){ System.out.println(s); } + + pointcut goCut(): cflow(this(Demo) && execution(void go())); + + pointcut demoExecs(): within(Demo) && execution(* *(..)); + + Object around(): demoExecs() && !execution(* go()) && goCut() { + println("Intercepted message: " + + thisJoinPointStaticPart.getSignature().getName()); + println("in class: " + + thisJoinPointStaticPart.getSignature().getDeclaringType().getName()); + printParameters(thisJoinPoint); + println("Running original method: \n" ); + Object result = proceed(); + println(" result: " + result ); + return result; + } + + static private void printParameters(JoinPoint jp) { + println("Arguments: " ); + Object[] args = jp.getArgs(); + String[] names = ((CodeSignature)jp.getSignature()).getParameterNames(); + Class[] types = ((CodeSignature)jp.getSignature()).getParameterTypes(); + for (int i = 0; i < args.length; i++) { + println(" " + i + ". " + names[i] + + " : " + types[i].getName() + + " = " + args[i]); + } + } +} +]]></programlisting> + </sect4> + + <sect4> + <title>Printing the parameters</title> + + <para> + The static portions of the parameter details, the name and + types of the parameters, can be accessed through the <ulink + url="../api/org/aspectj/lang/reflect/CodeSignature.html"><literal>CodeSignature</literal></ulink> + associated with the join point. All execution join points have code + signatures, so the cast to <literal>CodeSignature</literal> + cannot fail. </para> + + <para> + The dynamic portions of the parameter details, the actual + values of the parameters, are accessed directly from the execution + join point object. </para> + </sect4> + </sect3> + </sect2> + + <sect2 id="sec:RolesAndViews"> + <title>Roles and Views Using Introduction</title> + <titleabbrev id="sec:RolesAndViews:title">Roles and Views Using + Introduction</titleabbrev> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/introduction</filename>.)</para> + + <para>Like advice, pieces of introduction are members of an aspect. They + define new members that act as if they were defined on another + class. Unlike advice, introduction affects not only the behavior of the + application, but also the structural relationship between an + application's classes. </para> + + <para>This is crucial: Affecting the class structure of an application at + makes these modifications available to other components of the + application.</para> + + <para>Introduction modifies a class by adding or changing</para> + <itemizedlist spacing="compact"> + <listitem>member fields</listitem> + <listitem>member methods</listitem> + <listitem>nested classes</listitem> + </itemizedlist> + + <para>and by making the class</para> + + <itemizedlist spacing="compact"> + <listitem>implement interfaces</listitem> + <listitem>extend classes</listitem> + </itemizedlist> + + <para> + This example provides three illustrations of the use of introduction to + encapsulate roles or views of a class. The class we will be introducing + into, <classname>Point</classname>, is a simple class with rectangular + and polar coordinates. Our introduction will make the class + <classname>Point</classname>, in turn, cloneable, hashable, and + comparable. These facilities are provided by introduction forms without + having to modify the class <classname>Point</classname>. + </para> + + <sect3> + <title>The class <classname>Point</classname></title> + + <para>The class <classname>Point</classname> defines geometric points + whose interface includes polar and rectangular coordinates, plus some + simple operations to relocate points. <classname>Point</classname>'s + implementation has attributes for both its polar and rectangular + coordinates, plus flags to indicate which currently reflect the + position of the point. Some operations cause the polar coordinates to + be updated from the rectangular, and some have the opposite effect. + This implementation, which is in intended to give the minimum number + of conversions between coordinate systems, has the property that not + all the attributes stored in a <classname>Point</classname> object + are necessary to give a canonical representation such as might be + used for storing, comparing, cloning or making hash codes from + points. Thus the aspects, though simple, are not totally trivial. + </para> + + <para> + The diagram below gives an overview of the aspects and their + interaction with the class <classname>Point</classname>.</para> + + <para> + <inlinemediaobject> + <imageobject> + <imagedata fileref="aspects.gif"/> + </imageobject> + </inlinemediaobject> + </para> + <para></para> + + </sect3> + + <sect3> + <title>Making <classname>Point</classname>s Cloneable — The Aspect + <classname>CloneablePoint</classname></title> + + <para>This first example demonstrates the introduction of a interface + (<classname>Cloneable</classname>) and a method + (<function>clone</function>) into the class + <classname>Point</classname>. In Java, all objects inherit the method + <literal>clone</literal> from the class + <classname>Object</classname>, but an object is not cloneable unless + its class also implements the interface + <classname>Cloneable</classname>. In addition, classes frequently + have requirements over and above the simple bit-for-bit copying that + <literal>Object.clone</literal> does. In our case, we want to update + a <classname>Point</classname>'s coordinate systems before we + actually clone the <classname>Point</classname>. So we have to + override <literal>Object.clone</literal> with a new method that does + what we want. </para> + + <para>The <classname>CloneablePoint</classname> aspect uses the + <literal>declare parents</literal> form to introduce the interface + <classname>Cloneable</classname> into the class + <classname>Point</classname>. It then defines a method, + <literal>Point.clone</literal>, which overrides the method + <function>clone</function> that was inherited from + <classname>Object</classname>. <function>Point.clone</function> + updates the <classname>Point</classname>'s coordinate systems before + invoking its superclass' <function>clone</function> method.</para> + + <programlisting><![CDATA[ +public aspect CloneablePoint { + + declare parents: Point implements Cloneable; + + public Object Point.clone() throws CloneNotSupportedException { + // we choose to bring all fields up to date before cloning. + makeRectangular(); + makePolar(); + return super.clone(); + } + + public static void main(String[] args){ + Point p1 = new Point(); + Point p2 = null; + + p1.setPolar(Math.PI, 1.0); + try { + p2 = (Point)p1.clone(); + } catch (CloneNotSupportedException e) {} + System.out.println("p1 =" + p1 ); + System.out.println("p2 =" + p2 ); + + p1.rotate(Math.PI / -2); + System.out.println("p1 =" + p1 ); + System.out.println("p2 =" + p2 ); + } +} +]]></programlisting> + + <para>Note that since aspects define types just as classes define + types, we can define a <function>main</function> method that is + invocable from the command line to use as a test method.</para> + </sect3> + + <sect3> + <title>Making <classname>Point</classname>s Comparable — The + Aspect <classname>ComparablePoint</classname></title> + + <para>This second example introduces another interface and + method into the class <classname>Point</classname>.</para> + + <para>The interface <classname>Comparable</classname> defines the + single method <literal>compareTo</literal> which can be use to define + a natural ordering relation among the objects of a class that + implement it. </para> + + <para>The aspect <classname>ComparablePoint</classname> introduces + implements <classname>Comparable</classname> into + <classname>Point</classname> along with a + <literal>compareTo</literal> method that can be used to compare + <classname>Point</classname>s. A <classname>Point</classname> + <literal>p1</literal> is said to be less than + another <classname>Point</classname><literal> p2</literal> if + <literal>p1</literal> is closer to the origin. </para> + + <programlisting><![CDATA[ +public aspect ComparablePoint { + + declare parents: Point implements Comparable; + + public int Point.compareTo(Object o) { + return (int) (this.getRho() - ((Point)o).getRho()); + } + + public static void main(String[] args){ + Point p1 = new Point(); + Point p2 = new Point(); + + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.setRectangular(2,5); + p2.setRectangular(2,5); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p2.setRectangular(3,6); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.setPolar(Math.PI, 4); + p2.setPolar(Math.PI, 4); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.rotate(Math.PI / 4.0); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + + p1.offset(1,1); + System.out.println("p1 =?= p2 :" + p1.compareTo(p2)); + } +}]]></programlisting> + </sect3> + + <sect3> + <title>Making <classname>Point</classname>s Hashable — The Aspect + <classname>HashablePoint</classname></title> + + <para>The third aspect overrides two previously defined methods to + give to <classname>Point</classname> the hashing behavior we + want.</para> + + <para>The method <literal>Object.hashCode</literal> returns an unique + integer, suitable for use as a hash table key. Different + implementations are allowed return different integers, but must + return distinct integers for distinct objects, and the same integer + for objects that test equal. But since the default implementation + of <literal>Object.equal</literal> returns <literal>true</literal> + only when two objects are identical, we need to redefine both + <function>equals</function> and <function>hashCode</function> to work + correctly with objects of type <classname>Point</classname>. For + example, we want two <classname>Point</classname> objects to test + equal when they have the same <literal>x</literal> and + <literal>y</literal> values, or the same <literal>rho</literal> and + <literal>theta</literal> values, not just when they refer to the same + object. We do this by overriding the methods + <literal>equals</literal> and <literal>hashCode</literal> in the + class <classname>Point</classname>. </para> + + <para>The class <classname>HashablePoint</classname> introduces the + methods <literal>hashCode</literal> and <literal>equals</literal> + into the class <classname>Point</classname>. These methods use + <classname>Point</classname>'s rectangular coordinates to generate a + hash code and to test for equality. The <literal>x</literal> and + <literal>y</literal> coordinates are obtained using the appropriate + get methods, which ensure the rectangular coordinates are up-to-date + before returning their values. </para> + + <programlisting><![CDATA[ +public aspect HashablePoint { + + public int Point.hashCode() { + return (int) (getX() + getY() % Integer.MAX_VALUE); + } + + public boolean Point.equals(Object o) { + if (o == this) { return true; } + if (!(o instanceof Point)) { return false; } + Point other = (Point)o; + return (getX() == other.getX()) && (getY() == other.getY()); + } + + public static void main(String[] args) { + Hashtable h = new Hashtable(); + Point p1 = new Point(); + + p1.setRectangular(10, 10); + Point p2 = new Point(); + + p2.setRectangular(10, 10); + + System.out.println("p1 = " + p1); + System.out.println("p2 = " + p2); + System.out.println("p1.hashCode() = " + p1.hashCode()); + System.out.println("p2.hashCode() = " + p2.hashCode()); + + h.put(p1, "P1"); + System.out.println("Got: " + h.get(p2)); + } +} +]]></programlisting> + + <para> Again, we supply a <literal>main</literal> method in the aspect + for testing. + </para> + + </sect3> + + </sect2> + + </sect1> + +<!-- ============================================================ --> +<!-- ============================================================ --> + + <sect1> + <title>Development Aspects</title> + + <sect2> + <title>Tracing Aspects</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/tracing</filename>.) + </para> + + <sect3> + <title>Overview</title> + + <para> + Writing a class that provides tracing functionality is easy: a couple + of functions, a boolean flag for turning tracing on and off, a choice + for an output stream, maybe some code for formatting the output---these + are all elements that <classname>Trace</classname> classes have been + known to have. <classname>Trace</classname> classes may be highly + sophisticated, too, if the task of tracing the execution of a program + demands so. + </para> + + <para> + But developing the support for tracing is just one part of the effort + of inserting tracing into a program, and, most likely, not the biggest + part. The other part of the effort is calling the tracing functions at + appropriate times. In large systems, this interaction with the tracing + support can be overwhelming. Plus, tracing is one of those things that + slows the system down, so these calls should often be pulled out of the + system before the product is shipped. For these reasons, it is not + unusual for developers to write ad-hoc scripting programs that rewrite + the source code by inserting/deleting trace calls before and after the + method bodies. + </para> + + <para> + AspectJ can be used for some of these tracing concerns in a less ad-hoc + way. Tracing can be seen as a concern that crosscuts the entire system + and as such is amenable to encapsulation in an aspect. In addition, it + is fairly independent of what the system is doing. Therefore tracing is + one of those kind of system aspects that can potentially be plugged in + and unplugged without any side-effects in the basic functionality of + the system. + </para> + </sect3> + + <sect3> + <title>An Example Application</title> + + <para> + Throughout this example we will use a simple application that contains + only four classes. The application is about shapes. The + <classname>TwoDShape</classname> class is the root of the shape + hierarchy: + </para> + +<programlisting><![CDATA[ +public abstract class TwoDShape { + protected double x, y; + protected TwoDShape(double x, double y) { + this.x = x; this.y = y; + } + public double getX() { return x; } + public double getY() { return y; } + public double distance(TwoDShape s) { + double dx = Math.abs(s.getX() - x); + double dy = Math.abs(s.getY() - y); + return Math.sqrt(dx*dx + dy*dy); + } + public abstract double perimeter(); + public abstract double area(); + public String toString() { + return (" @ (" + String.valueOf(x) + ", " + String.valueOf(y) + ") "); + } +} +]]></programlisting> + + <para> + <classname>TwoDShape</classname> has two subclasses, + <classname>Circle</classname> and <classname>Square</classname>: + </para> + +<programlisting><![CDATA[ +public class Circle extends TwoDShape { + protected double r; + public Circle(double x, double y, double r) { + super(x, y); this.r = r; + } + public Circle(double x, double y) { this( x, y, 1.0); } + public Circle(double r) { this(0.0, 0.0, r); } + public Circle() { this(0.0, 0.0, 1.0); } + public double perimeter() { + return 2 * Math.PI * r; + } + public double area() { + return Math.PI * r*r; + } + public String toString() { + return ("Circle radius = " + String.valueOf(r) + super.toString()); + } +} +]]></programlisting> + +<programlisting><![CDATA[ +public class Square extends TwoDShape { + protected double s; // side + public Square(double x, double y, double s) { + super(x, y); this.s = s; + } + public Square(double x, double y) { this( x, y, 1.0); } + public Square(double s) { this(0.0, 0.0, s); } + public Square() { this(0.0, 0.0, 1.0); } + public double perimeter() { + return 4 * s; + } + public double area() { + return s*s; + } + public String toString() { + return ("Square side = " + String.valueOf(s) + super.toString()); + } +} +]]></programlisting> + + <para> + To run this application, compile the classes. You can do it with or + without ajc, the AspectJ compiler. If you've installed AspectJ, go to + the directory + <filename><replaceable>InstallDir</replaceable>/examples</filename> and + type: + </para> + +<programlisting> +ajc -argfile tracing/notrace.lst +</programlisting> + + <para>To run the program, type</para> + +<programlisting> +java tracing.ExampleMain +</programlisting> + + <para>(we don't need anything special on the classpath since this is pure + Java code). You should see the following output:</para> + +<programlisting><![CDATA[ +c1.perimeter() = 12.566370614359172 +c1.area() = 12.566370614359172 +s1.perimeter() = 4.0 +s1.area() = 1.0 +c2.distance(c1) = 4.242640687119285 +s1.distance(c1) = 2.23606797749979 +s1.toString(): Square side = 1.0 @ (1.0, 2.0) +]]></programlisting> + + </sect3> + <sect3> + <title>Tracing—Version 1</title> + + <para> + In a first attempt to insert tracing in this application, we will start + by writing a <classname>Trace</classname> class that is exactly what we + would write if we didn't have aspects. The implementation is in + <filename>version1/Trace.java</filename>. Its public interface is: + </para> + +<programlisting><![CDATA[ +public class Trace { + public static int TRACELEVEL = 0; + public static void initStream(PrintStream s) {...} + public static void traceEntry(String str) {...} + public static void traceExit(String str) {...} +} +]]></programlisting> + + <para> + If we didn't have AspectJ, we would have to insert calls to + <literal>traceEntry</literal> and <literal>traceExit</literal> in all + methods and constructors we wanted to trace, and to initialize + <literal>TRACELEVEL</literal> and the stream. If we wanted to trace all + the methods and constructors in our example, that would amount to + around 40 calls, and we would hope we had not forgotten any method. But + we can do that more consistently and reliably with the following + aspect (found in <filename>version1/TraceMyClasses.java</filename>): + </para> + +<programlisting><![CDATA[ +aspect TraceMyClasses { + pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square); + pointcut myConstructor(): myClass() && execution(new(..)); + pointcut myMethod(): myClass() && execution(* *(..)); + + before (): myConstructor() { + Trace.traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myConstructor() { + Trace.traceExit("" + thisJoinPointStaticPart.getSignature()); + } + + before (): myMethod() { + Trace.traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myMethod() { + Trace.traceExit("" + thisJoinPointStaticPart.getSignature()); + } +}]]></programlisting> + + <para> + This aspect performs the tracing calls at appropriate times. According + to this aspect, tracing is performed at the entrance and exit of every + method and constructor defined within the shape hierarchy. + </para> + + <para> + What is printed at before and after each of the traced + join points is the signature of the method executing. Since the + signature is static information, we can get it through + <literal>thisJoinPointStaticPart</literal>. + </para> + + <para> + To run this version of tracing, go to the directory + <filename><replaceable>InstallDir</replaceable>/examples</filename> and + type: + </para> + +<programlisting><![CDATA[ + ajc -argfile tracing/tracev1.lst +]]></programlisting> + + <para> + Running the main method of + <classname>tracing.version1.TraceMyClasses</classname> should produce + the output: + </para> + +<programlisting><![CDATA[ + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.Circle(double) + <-- tracing.Circle(double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Square(double, double, double) + <-- tracing.Square(double, double, double) + --> tracing.Square(double, double) + <-- tracing.Square(double, double) + --> double tracing.Circle.perimeter() + <-- double tracing.Circle.perimeter() +c1.perimeter() = 12.566370614359172 + --> double tracing.Circle.area() + <-- double tracing.Circle.area() +c1.area() = 12.566370614359172 + --> double tracing.Square.perimeter() + <-- double tracing.Square.perimeter() +s1.perimeter() = 4.0 + --> double tracing.Square.area() + <-- double tracing.Square.area() +s1.area() = 1.0 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +c2.distance(c1) = 4.242640687119285 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +s1.distance(c1) = 2.23606797749979 + --> String tracing.Square.toString() + --> String tracing.TwoDShape.toString() + <-- String tracing.TwoDShape.toString() + <-- String tracing.Square.toString() +s1.toString(): Square side = 1.0 @ (1.0, 2.0) +]]></programlisting> + + <para> + When <filename>TraceMyClasses.java</filename> is not provided to + <command>ajc</command>, the aspect does not have any affect on the + system and the tracing is unplugged. + </para> + </sect3> + + <sect3> + <title>Tracing—Version 2</title> + + <para> + Another way to accomplish the same thing would be to write a reusable + tracing aspect that can be used not only for these application classes, + but for any class. One way to do this is to merge the tracing + functionality of <literal>Trace—version1</literal> with the + crosscutting support of + <literal>TraceMyClasses—version1</literal>. We end up with a + <literal>Trace</literal> aspect (found in + <filename>version2/Trace.java</filename>) with the following public + interface + </para> + +<programlisting><![CDATA[ +abstract aspect Trace { + + public static int TRACELEVEL = 2; + public static void initStream(PrintStream s) {...} + protected static void traceEntry(String str) {...} + protected static void traceExit(String str) {...} + abstract pointcut myClass(); +} +]]></programlisting> + + <para> + In order to use it, we need to define our own subclass that knows about + our application classes, in <filename>version2/TraceMyClasses.java</filename>: + </para> + +<programlisting><![CDATA[ +public aspect TraceMyClasses extends Trace { + pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square); + + public static void main(String[] args) { + Trace.TRACELEVEL = 2; + Trace.initStream(System.err); + ExampleMain.main(args); + } +} +]]></programlisting> + + <para> + Notice that we've simply made the pointcut <literal>classes</literal>, + that was an abstract pointcut in the super-aspect, concrete. To run + this version of tracing, go to the directory + <filename>examples</filename> and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile tracing/tracev2.lst +]]></programlisting> + + <para> + The file tracev2.lst lists the application classes as well as this + version of the files Trace.java and TraceMyClasses.java. Running the + main method of <classname>tracing.version2.TraceMyClasses</classname> + should output exactly the same trace information as that from version + 1. + </para> + + <para> + The entire implementation of the new <classname>Trace</classname> class + is: + </para> + +<programlisting><![CDATA[ +abstract aspect Trace { + + // implementation part + + public static int TRACELEVEL = 2; + protected static PrintStream stream = System.err; + protected static int callDepth = 0; + + public static void initStream(PrintStream s) { + stream = s; + } + protected static void traceEntry(String str) { + if (TRACELEVEL == 0) return; + if (TRACELEVEL == 2) callDepth++; + printEntering(str); + } + protected static void traceExit(String str) { + if (TRACELEVEL == 0) return; + printExiting(str); + if (TRACELEVEL == 2) callDepth--; + } + private static void printEntering(String str) { + printIndent(); + stream.println("--> " + str); + } + private static void printExiting(String str) { + printIndent(); + stream.println("<-- " + str); + } + private static void printIndent() { + for (int i = 0; i < callDepth; i++) + stream.print(" "); + } + + // protocol part + + abstract pointcut myClass(); + + pointcut myConstructor(): myClass() && execution(new(..)); + pointcut myMethod(): myClass() && execution(* *(..)); + + before(): myConstructor() { + traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myConstructor() { + traceExit("" + thisJoinPointStaticPart.getSignature()); + } + + before(): myMethod() { + traceEntry("" + thisJoinPointStaticPart.getSignature()); + } + after(): myMethod() { + traceExit("" + thisJoinPointStaticPart.getSignature()); + } +} +]]></programlisting> + + <para> + This version differs from version 1 in several subtle ways. The first + thing to notice is that this <classname>Trace</classname> class merges + the functional part of tracing with the crosscutting of the tracing + calls. That is, in version 1, there was a sharp separation between the + tracing support (the class <classname>Trace</classname>) and the + crosscutting usage of it (by the class + <classname>TraceMyClasses</classname>). In this version those two + things are merged. That's why the description of this class explicitly + says that "Trace messages are printed before and after constructors and + methods are," which is what we wanted in the first place. That is, the + placement of the calls, in this version, is established by the aspect + class itself, leaving less opportunity for misplacing calls.</para> + + <para> + A consequence of this is that there is no need for providing traceEntry + and traceExit as public operations of this class. You can see that they + were classified as protected. They are supposed to be internal + implementation details of the advice. + </para> + + <para> + The key piece of this aspect is the abstract pointcut classes that + serves as the base for the definition of the pointcuts constructors and + methods. Even though <classname>classes</classname> is abstract, and + therefore no concrete classes are mentioned, we can put advice on it, + as well as on the pointcuts that are based on it. The idea is "we don't + know exactly what the pointcut will be, but when we do, here's what we + want to do with it." In some ways, abstract pointcuts are similar to + abstract methods. Abstract methods don't provide the implementation, + but you know that the concrete subclasses will, so you can invoke those + methods. + </para> + </sect3> + </sect2> + </sect1> + +<!-- ============================================================ --> +<!-- ============================================================ --> + + <sect1> + <title>Production Aspects</title> + + <!-- ==================== --> + + <sect2><!-- A Bean Aspect --> + <title>A Bean Aspect</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/bean</filename>.) + </para> + + <para> + This example examines an aspect that makes Point objects into a Java beans + with bound properties. </para> + + <sect3> + <title>Introduction</title> + <para> + Java beans are reusable software components that can be visually + manipulated in a builder tool. The requirements for an object to be a + bean are few. Beans must define a no-argument constructor and must be + either <classname>Serializable</classname> or + <classname>Externalizable</classname>. Any properties of the object + that are to be treated as bean properties should be indicated by the + presence of appropriate <literal>get</literal> and + <literal>set</literal> methods whose names are + <literal>get</literal><emphasis>property</emphasis> and + <literal>set </literal><emphasis>property</emphasis> + where <emphasis>property</emphasis> is the name of a field in the bean + class. Some bean properties, known as bound properties, fire events + whenever their values change so that any registered listeners (such as, + other beans) will be informed of those changes. Making a bound property + involves keeping a list of registered listeners, and creating and + dispatching event objects in methods that change the property values, + such as set<emphasis>property</emphasis> methods.</para> + + <para> + <classname>Point</classname> is a simple class representing points with + rectangular coordinates. <classname>Point</classname> does not know + anything about being a bean: there are set methods for + <literal>x</literal> and <literal>y</literal> but they do not fire + events, and the class is not serializable. Bound is an aspect that + makes <classname>Point</classname> a serializable class and makes its + <literal>get</literal> and <literal>set</literal> methods support the + bound property protocol. + </para> + </sect3> + + <sect3> + <title>The Class <classname>Point</classname></title> + + <para> + The class <classname>Point</classname> is a very simple class with + trivial getters and setters, and a simple vector offset method. + </para> + + <programlisting><![CDATA[ +class Point { + + protected int x = 0; + protected int y = 0; + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public void setRectangular(int newX, int newY) { + setX(newX); + setY(newY); + } + + public void setX(int newX) { + x = newX; + } + + public void setY(int newY) { + y = newY; + } + + public void offset(int deltaX, int deltaY) { + setRectangular(x + deltaX, y + deltaY); + } + + public String toString() { + return "(" + getX() + ", " + getY() + ")" ; + } +}]]></programlisting> + + </sect3> + + <sect3> + <title>The Aspect <classname>BoundPoint</classname></title> + + <para> + The aspect <classname>BoundPoint</classname> adds "beanness" to + <classname>Point</classname> objects. The first thing it does is + privately introduce a reference to an instance of + <classname>PropertyChangeSupport</classname> into all + <classname>Point</classname> objects. The property change + support object must be constructed with a reference to the bean for + which it is providing support, so it is initialized by passing it this, + an instance of <classname>Point</classname>. The support field is + privately introduced, so only the code in the aspect can refer to it. + </para> + + <para> + Methods for registering and managing listeners for property change + events are introduced into <classname>Point</classname> by the + introductions. These methods delegate the work to the + property change support object. + </para> + + <para> + The introduction also makes <classname>Point</classname> implement the + <classname>Serializable</classname> interface. Implementing + <classname>Serializable</classname> does not require any methods to be + implemented. Serialization for <classname>Point</classname> objects is + provided by the default serialization method. + </para> + + <para> + The pointcut <function>setters</function> names the + <literal>set</literal> methods: reception by a + <classname>Point</classname> object of any method whose name begins + with '<literal>set</literal>' and takes one parameter. The around + advice on <literal>setters()</literal> stores the values + of the <literal>X</literal> and <literal>Y</literal> properties, calls + the original <literal>set</literal> method and then fires the + appropriate property change event according to which set method was + called. Note that the call to the method proceed needs to pass along + the <literal>Point p</literal>. The rule of thumb is that context that + an around advice exposes must be passed forward to continue. + </para> + +<programlisting><![CDATA[ +aspect BoundPoint { + private PropertyChangeSupport Point.support = new PropertyChangeSupport(this); + + public void Point.addPropertyChangeListener(PropertyChangeListener listener){ + support.addPropertyChangeListener(listener); + } + + public void Point.addPropertyChangeListener(String propertyName, + PropertyChangeListener listener){ + + support.addPropertyChangeListener(propertyName, listener); + } + + public void Point.removePropertyChangeListener(String propertyName, + PropertyChangeListener listener) { + support.removePropertyChangeListener(propertyName, listener); + } + + public void Point.removePropertyChangeListener(PropertyChangeListener listener) { + support.removePropertyChangeListener(listener); + } + + public void Point.hasListeners(String propertyName) { + support.hasListeners(propertyName); + } + + declare parents: Point implements Serializable; + + pointcut setter(Point p): call(void Point.set*(*)) && target(p); + + void around(Point p): setter(p) { + String propertyName = + thisJoinPointStaticPart.getSignature().getName().substring("set".length()); + int oldX = p.getX(); + int oldY = p.getY(); + proceed(p); + if (propertyName.equals("X")){ + firePropertyChange(p, propertyName, oldX, p.getX()); + } else { + firePropertyChange(p, propertyName, oldY, p.getY()); + } + } + + void firePropertyChange(Point p, + String property, + double oldval, + double newval) { + p.support.firePropertyChange(property, + new Double(oldval), + new Double(newval)); + } +} +]]></programlisting> + + </sect3> + + <sect3> + <title>The Test Program</title> + + <para> + The test program registers itself as a property change listener to a + <literal>Point</literal> object that it creates and then performs + simple manipulation of that point: calling its set methods and the + offset method. Then it serializes the point and writes it to a file and + then reads it back. The result of saving and restoring the point is that + a new point is created. + </para> + +<programlisting><![CDATA[ + class Demo implements PropertyChangeListener { + + static final String fileName = "test.tmp"; + + public void propertyChange(PropertyChangeEvent e){ + System.out.println("Property " + e.getPropertyName() + " changed from " + + e.getOldValue() + " to " + e.getNewValue() ); + } + + public static void main(String[] args){ + Point p1 = new Point(); + p1.addPropertyChangeListener(new Demo()); + System.out.println("p1 =" + p1); + p1.setRectangular(5,2); + System.out.println("p1 =" + p1); + p1.setX( 6 ); + p1.setY( 3 ); + System.out.println("p1 =" + p1); + p1.offset(6,4); + System.out.println("p1 =" + p1); + save(p1, fileName); + Point p2 = (Point) restore(fileName); + System.out.println("Had: " + p1); + System.out.println("Got: " + p2); + } + ... + } +]]></programlisting> + + </sect3> + <sect3> + <title>Compiling and Running the Example</title> + <para>To compile and run this example, go to the examples directory and type: + </para> + +<programlisting><![CDATA[ +ajc -argfile bean/files.lst +java bean.Demo +]]></programlisting> + + </sect3> + </sect2> + + <!-- ==================== --> + + <sect2><!-- The Subject/Observer Protocol --> + <title>The Subject/Observer Protocol</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/observer</filename>.) + </para> + + <para> + This demo illustrates how the Subject/Observer design pattern can be + coded with aspects. </para> + + <sect3> + <title>Overview</title> + <para> + The demo consists of the following: A colored label is a renderable + object that has a color that cycles through a set of colors, and a + number that records the number of cycles it has been through. A button + is an action item that records when it is clicked. + </para> + + <para> + With these two kinds of objects, we can build up a Subject/Observer + relationship in which colored labels observe the clicks of buttons; + that is, where colored labels are the observers and buttons are the + subjects. + </para> + + <para> + The demo is designed and implemented using the Subject/Observer design + pattern. The remainder of this example explains the classes and aspects + of this demo, and tells you how to run it. + </para> + </sect3> + + <sect3> + <title>Generic Components</title> + + <para> + The generic parts of the protocol are the interfaces + <classname>Subject</classname> and <classname>Observer</classname>, and + the abstract aspect <classname>SubjectObserverProtocol</classname>. The + <classname>Subject</classname> interface is simple, containing methods + to add, remove, and view <classname>Observer</classname> objects, and a + method for getting data about state changes: + </para> + +<programlisting><![CDATA[ + interface Subject { + void addObserver(Observer obs); + void removeObserver(Observer obs); + Vector getObservers(); + Object getData(); + } +]]></programlisting> + + <para> The <classname>Observer</classname> interface is just as simple, + with methods to set and get <classname>Subject</classname> objects, and + a method to call when the subject gets updated. + </para> + +<programlisting><![CDATA[ + interface Observer { + void setSubject(Subject s); + Subject getSubject(); + void update(); + } +]]></programlisting> + + <para> + The <classname>SubjectObserverProtocol</classname> aspect contains + within it all of the generic parts of the protocol, namely, how to fire + the <classname>Observer</classname> objects' update methods when some + state changes in a subject. + </para> + +<programlisting><![CDATA[ + abstract aspect SubjectObserverProtocol { + + abstract pointcut stateChanges(Subject s); + + after(Subject s): stateChanges(s) { + for (int i = 0; i < s.getObservers().size(); i++) { + ((Observer)s.getObservers().elementAt(i)).update(); + } + } + + private Vector Subject.observers = new Vector(); + public void Subject.addObserver(Observer obs) { + observers.addElement(obs); + obs.setSubject(this); + } + public void Subject.removeObserver(Observer obs) { + observers.removeElement(obs); + obs.setSubject(null); + } + public Vector Subject.getObservers() { return observers; } + + private Subject Observer.subject = null; + public void Observer.setSubject(Subject s) { subject = s; } + public Subject Observer.getSubject() { return subject; } + + } +]]></programlisting> + + <para> + Note that this aspect does three things. It define an abstract pointcut + that extending aspects can override. It defines advice that should run + after the join points of the pointcut. And it introduces state and + behavior onto the <classname>Subject</classname> and + <classname>Observer</classname> interfaces. + </para> + </sect3> + + <sect3> + <title>Application Classes</title> + + <para> <classname>Button</classname> objects extend + <classname>java.awt.Button</classname>, and all they do is make sure + the <literal>void click()</literal> method is called whenever a button + is clicked. + </para> + +<programlisting><![CDATA[ + class Button extends java.awt.Button { + + static final Color defaultBackgroundColor = Color.gray; + static final Color defaultForegroundColor = Color.black; + static final String defaultText = "cycle color"; + + Button(Display display) { + super(); + setLabel(defaultText); + setBackground(defaultBackgroundColor); + setForeground(defaultForegroundColor); + addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Button.this.click(); + } + }); + display.addToFrame(this); + } + + public void click() {} + + } +]]></programlisting> + + <para> + Note that this class knows nothing about being a Subject. + </para> + <para> + ColorLabel objects are labels that support the void colorCycle() + method. Again, they know nothing about being an observer. + </para> + +<programlisting><![CDATA[ + class ColorLabel extends Label { + + ColorLabel(Display display) { + super(); + display.addToFrame(this); + } + + final static Color[] colors = {Color.red, Color.blue, + Color.green, Color.magenta}; + private int colorIndex = 0; + private int cycleCount = 0; + void colorCycle() { + cycleCount++; + colorIndex = (colorIndex + 1) % colors.length; + setBackground(colors[colorIndex]); + setText("" + cycleCount); + } + } +]]></programlisting> + + <para> + Finally, the <classname>SubjectObserverProtocolImpl</classname> + implements the subject/observer protocol, with + <classname>Button</classname> objects as subjects and + <classname>ColorLabel</classname> objects as observers: + </para> + +<programlisting><![CDATA[ +package observer; + +import java.util.Vector; + +aspect SubjectObserverProtocolImpl extends SubjectObserverProtocol { + + declare parents: Button implements Subject; + public Object Button.getData() { return this; } + + declare parents: ColorLabel implements Observer; + public void ColorLabel.update() { + colorCycle(); + } + + pointcut stateChanges(Subject s): + target(s) && + call(void Button.click()); + +}]]></programlisting> + + <para> + It does this by introducing the appropriate interfaces onto the + <classname>Button</classname> and <classname>ColorLabel</classname> + classes, making sure the methods required by the interfaces are + implemented, and providing a definition for the + <literal>stateChanges</literal> pointcut. Now, every time a + <classname>Button</classname> is clicked, all + <classname>ColorLabel</classname> objects observing that button will + <literal>colorCycle</literal>. + </para> + </sect3> + + <sect3> + <title>Compiling and Running</title> + + <para> <classname>Demo</classname> is the top class that starts this + demo. It instantiates a two buttons and three observers and links them + together as subjects and observers. So to run the demo, go to the + <filename>examples</filename> directory and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile observer/files.lst + java observer.Demo +]]></programlisting> + + </sect3> + </sect2> + + <!-- ==================== --> + + <sect2> + <title>A Simple Telecom Simulation</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/telecom</filename>.) + </para> + + <para> + This example illustrates some ways that dependent concerns can be encoded + with aspects. It uses an example system comprising a simple model of + telephone connections to which timing and billing features are added + using aspects, where the billing feature depends upon the timing feature. + </para> + + <sect3> + <title>The Application</title> + + <para> + The example application is a simple simulation of a telephony system in + which customers make, accept, merge and hang-up both local and long + distance calls. The application architecture is in three layers. + </para> + <itemizedlist> + <listitem> + <para> + The basic objects provide basic functionality to simulate + customers, calls and connections (regular calls have one + connection, conference calls have more than one). + </para> + </listitem> + + <listitem> + <para> + The timing feature is concerned with timing the connections and + keeping the total connection time per customer. Aspects are used to + add a timer to each connection and to manage the total time per + customer. + </para> + </listitem> + + <listitem> + <para> + The billing feature is concerned with charging customers for the + calls they make. Aspects are used to calculate a charge per + connection and, upon termination of a connection, to add the charge + to the appropriate customer's bill. The billing aspect builds upon + the timing aspect: it uses a pointcut defined in Timing and it uses + the timers that are associated with connections. + </para> + </listitem> + </itemizedlist> + <para> + The simulation of system has three configurations: basic, timing and + billing. Programs for the three configurations are in classes + <classname>BasicSimulation</classname>, + <classname>TimingSimulation</classname> and + <classname>BillingSimulation</classname>. These share a common + superclass <classname>AbstractSimulation</classname>, which defines the + method run with the simulation itself and the method wait used to + simulate elapsed time. + </para> + </sect3> + + <sect3> + <title>The Basic Objects</title> + + <para> + The telecom simulation comprises the classes + <classname>Customer</classname>, <classname>Call</classname> and the + abstract class <classname>Connection</classname> with its two concrete + subclasses <classname>Local</classname> and + <classname>LongDistance</classname>. Customers have a name and a + numeric area code. They also have methods for managing calls. Simple + calls are made between one customer (the caller) and another (the + receiver), a <classname>Connection</classname> object is used to + connect them. Conference calls between more than two customers will + involve more than one connection. A customer may be involved in many + calls at one time. + <inlinemediaobject> + <imageobject> + <imagedata fileref="telecom.gif"/> + </imageobject> + </inlinemediaobject> + </para> + </sect3> + + <sect3> + <title>The Class <classname>Customer</classname></title> + + <para> + <classname>Customer</classname> has methods <literal>call</literal>, + <literal>pickup</literal>, <literal>hangup</literal> and + <literal>merge</literal> for managing calls. + </para> + +<programlisting><![CDATA[ +public class Customer { + + private String name; + private int areacode; + private Vector calls = new Vector(); + + protected void removeCall(Call c){ + calls.removeElement(c); + } + + protected void addCall(Call c){ + calls.addElement(c); + } + + public Customer(String name, int areacode) { + this.name = name; + this.areacode = areacode; + } + + public String toString() { + return name + "(" + areacode + ")"; + } + + public int getAreacode(){ + return areacode; + } + + public boolean localTo(Customer other){ + return areacode == other.areacode; + } + + public Call call(Customer receiver) { + Call call = new Call(this, receiver); + addCall(call); + return call; + } + + public void pickup(Call call) { + call.pickup(); + addCall(call); + } + + public void hangup(Call call) { + call.hangup(this); + removeCall(call); + } + + public void merge(Call call1, Call call2){ + call1.merge(call2); + removeCall(call2); + } + } +]]></programlisting> + + </sect3> + + <sect3> + <title>The Class <classname>Call</classname></title> + + <para> + Calls are created with a caller and receiver who are customers. If the + caller and receiver have the same area code then the call can be + established with a <classname>Local</classname> connection (see below), + otherwise a <classname>LongDistance</classname> connection is required. + A call comprises a number of connections between customers. Initially + there is only the connection between the caller and receiver but + additional connections can be added if calls are merged to form + conference calls. + </para> + </sect3> + + <sect3> + <title>The Class <classname>Connection</classname></title> + + <para>The class <classname>Connection</classname> models the physical + details of establishing a connection between customers. It does this + with a simple state machine (connections are initially + <literal>PENDING</literal>, then <literal>COMPLETED</literal> and + finally <literal>DROPPED</literal>). Messages are printed to the + console so that the state of connections can be observed. Connection is + an abstract class with two concrete subclasses: + <classname>Local</classname> and <classname>LongDistance</classname>. + </para> + +<programlisting><![CDATA[ + abstract class Connection { + + public static final int PENDING = 0; + public static final int COMPLETE = 1; + public static final int DROPPED = 2; + + Customer caller, receiver; + private int state = PENDING; + + Connection(Customer a, Customer b) { + this.caller = a; + this.receiver = b; + } + + public int getState(){ + return state; + } + + public Customer getCaller() { return caller; } + + public Customer getReceiver() { return receiver; } + + void complete() { + state = COMPLETE; + System.out.println("connection completed"); + } + + void drop() { + state = DROPPED; + System.out.println("connection dropped"); + } + + public boolean connects(Customer c){ + return (caller == c || receiver == c); + } + + } +]]></programlisting> + + </sect3> + + <sect3> + <title>The Class Local</title> + +<programlisting><![CDATA[ + class Local extends Connection { + Local(Customer a, Customer b) { + super(a, b); + System.out.println("[new local connection from " + + a + " to " + b + "]"); + } + } +]]></programlisting> + + </sect3> + + <sect3> + <title>The Class LongDistance</title> + +<programlisting><![CDATA[ + class LongDistance extends Connection { + LongDistance(Customer a, Customer b) { + super(a, b); + System.out.println("[new long distance connection from " + + a + " to " + b + "]"); + } + } +]]></programlisting> + + </sect3> + + <sect3> + <title>Compiling and Running the Basic Simulation</title> + + <para> + The source files for the basic system are listed in the file + <filename>basic.lst</filename>. To build and run the basic system, in a + shell window, type these commands: + </para> + +<programlisting><![CDATA[ +ajc -argfile telecom/basic.lst +java telecom.BasicSimulation +]]></programlisting> + + </sect3> + + <sect3> + <title>Timing</title> + <para> + The <classname>Timing</classname> aspect keeps track of total + connection time for each <classname>Customer</classname> by starting + and stopping a timer associated with each connection. It uses some + helper classes: + </para> + + <sect4> + <title>The Class <classname>Timer</classname></title> + + <para> + A <classname>Timer</classname> object simply records the current time + when it is started and stopped, and returns their difference when + asked for the elapsed time. The aspect + <classname>TimerLog</classname> (below) can be used to cause the + start and stop times to be printed to standard output. + </para> + +<programlisting><![CDATA[ + class Timer { + long startTime, stopTime; + + public void start() { + startTime = System.currentTimeMillis(); + stopTime = startTime; + } + + public void stop() { + stopTime = System.currentTimeMillis(); + } + + public long getTime() { + return stopTime - startTime; + } + } +]]></programlisting> + + </sect4> + </sect3> + + <sect3> + <title>The Aspect <classname>TimerLog</classname></title> + + <para> + The aspect <classname>TimerLog</classname> can be included in a + build to get the timer to announce when it is started and stopped. + </para> + +<programlisting><![CDATA[ +public aspect TimerLog { + + after(Timer t): target(t) && call(* Timer.start()) { + System.err.println("Timer started: " + t.startTime); + } + + after(Timer t): target(t) && call(* Timer.stop()) { + System.err.println("Timer stopped: " + t.stopTime); + } +} +]]></programlisting> + + </sect3> + + <sect3> + <title>The Aspect <classname>Timing</classname></title> + + <para> + The aspect <classname>Timing</classname> introduces attribute + <literal>totalConnectTime</literal> into the class + <classname>Customer</classname> to store the accumulated connection + time per <classname>Customer</classname>. It introduces attribute + timer into <classname>Connection</classname> to associate a timer + with each <classname>Connection</classname>. Two pieces of after + advice ensure that the timer is started when a connection is + completed and and stopped when it is dropped. The pointcut + <literal>endTiming</literal> is defined so that it can be used by the + <classname>Billing</classname> aspect. + </para> + +<programlisting><![CDATA[ +public aspect Timing { + + public long Customer.totalConnectTime = 0; + + public long getTotalConnectTime(Customer cust) { + return cust.totalConnectTime; + } + private Timer Connection.timer = new Timer(); + public Timer getTimer(Connection conn) { return conn.timer; } + + after (Connection c): target(c) && call(void Connection.complete()) { + getTimer(c).start(); + } + + pointcut endTiming(Connection c): target(c) && + call(void Connection.drop()); + + after(Connection c): endTiming(c) { + getTimer(c).stop(); + c.getCaller().totalConnectTime += getTimer(c).getTime(); + c.getReceiver().totalConnectTime += getTimer(c).getTime(); + } +}]]></programlisting> + + </sect3> + + <sect3> + <title>Billing</title> + + <para> + The Billing system adds billing functionality to the telecom + application on top of timing. + </para> + + <sect4> + <title>The Aspect <classname>Billing</classname></title> + + <para> + The aspect <classname>Billing</classname> introduces attribute + <literal>payer</literal> into <classname>Connection</classname> + to indicate who initiated the call and therefore who is + responsible to pay for it. It also introduces method + <literal>callRate</literal> into <classname>Connection</classname> + so that local and long distance calls can be charged + differently. The call charge must be calculated after the timer is + stopped; the after advice on pointcut + <literal>Timing.endTiming</literal> does this and + <classname>Billing</classname> dominates Timing to make + sure that this advice runs after <classname>Timing's</classname> + advice on the same join point. It introduces attribute + <literal>totalCharge</literal> and its associated methods into + <classname>Customer</classname> (to manage the + customer's bill information. + </para> + +<programlisting><![CDATA[ +public aspect Billing dominates Timing { + // domination required to get advice on endtiming in the right order + + public static final long LOCAL_RATE = 3; + public static final long LONG_DISTANCE_RATE = 10; + + + public Customer Connection.payer; + public Customer getPayer(Connection conn) { return conn.payer; } + + after(Customer cust) returning (Connection conn): + args(cust, ..) && call(Connection+.new(..)) { + conn.payer = cust; + } + + public abstract long Connection.callRate(); + + + public long LongDistance.callRate() { return LONG_DISTANCE_RATE; } + public long Local.callRate() { return LOCAL_RATE; } + + + after(Connection conn): Timing.endTiming(conn) { + long time = Timing.aspectOf().getTimer(conn).getTime(); + long rate = conn.callRate(); + long cost = rate * time; + getPayer(conn).addCharge(cost); + } + + + public long Customer.totalCharge = 0; + public long getTotalCharge(Customer cust) { return cust.totalCharge; } + + public void Customer.addCharge(long charge){ + totalCharge += charge; + } +} +]]></programlisting> + + </sect4> + </sect3> + + <sect3> + <title>Accessing the Introduced State</title> + + <para> + Both the aspects <classname>Timing</classname> and + <classname>Billing</classname> contain the definition of operations + that the rest of the system may want to access. For example, when + running the simulation with one or both aspects, we want to find out + how much time each customer spent on the telephone and how big their + bill is. That information is also stored in the classes, but they are + accessed through static methods of the aspects, since the state they + refer to is private to the aspect. + </para> + + <para> + Take a look at the file <filename>TimingSimulation.java</filename>. The + most important method of this class is the method + <filename>report(Customer c)</filename>, which is used in the method + run of the superclass <classname>AbstractSimulation</classname>. This + method is intended to print out the status of the customer, with + respect to the <classname>Timing</classname> feature. + </para> + +<programlisting><![CDATA[ + protected void report(Customer c){ + Timing t = Timing.aspectOf(); + System.out.println(c + " spent " + t.getTotalConnectTime(c)); + } +]]></programlisting> + + </sect3> + + <sect3> + <title>Compiling and Running</title> + + <para> + The files timing.lst and billing.lst contain file lists for the timing + and billing configurations. To build and run the application with only + the timing feature, go to the directory examples and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile telecom/timing.lst + java telecom.TimingSimulation +]]></programlisting> + + <para> + To build and run the application with the timing and billing features, + go to the directory examples and type: + </para> + +<programlisting><![CDATA[ + ajc -argfile telecom/billing.lst + java telecom.BillingSimulation +]]></programlisting> + + </sect3> + + <sect3> + <title>Discussion</title> + + <para> + There are some explicit dependencies between the aspects Billing and + Timing: + <itemizedlist> + <listitem> + <para> + Billing is declared to dominate Timing so that Billing's after + advice runs after that of Timing when they are on the same join + point. + </para> + </listitem> + + <listitem> + <para> + Billing uses the pointcut Timing.endTiming. + </para> + </listitem> + + <listitem> + <para> + Billing needs access to the timer associated with a connection. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> + </sect1> + +<!-- ============================================================ --> +<!-- ============================================================ --> + + <sect1> + <title>Reusable Aspects</title> + + <sect2> + <title>Tracing Aspects Revisited</title> + + <para>(The code for this example is in + <filename><replaceable>InstallDir</replaceable>/examples/tracing</filename>.) + </para> + + <sect3> + <title>Tracing—Version 3</title> + + <para> + One advantage of not exposing the methods traceEntry and traceExit as + public operations is that we can easily change their interface without + any dramatic consequences in the rest of the code. + </para> + + <para> + Consider, again, the program without AspectJ. Suppose, for example, + that at some point later the requirements for tracing change, stating + that the trace messages should always include the string representation + of the object whose methods are being traced. This can be achieved in + at least two ways. One way is keep the interface of the methods + <literal>traceEntry</literal> and <literal>traceExit</literal> as it + was before, + </para> + +<programlisting><![CDATA[ + public static void traceEntry(String str); + public static void traceExit(String str); +]]></programlisting> + + <para> + In this case, the caller is responsible for ensuring that the string + representation of the object is part of the string given as argument. + So, calls must look like: + </para> + +<programlisting><![CDATA[ + Trace.traceEntry("Square.distance in " + toString()); +]]></programlisting> + + <para> + Another way is to enforce the requirement with a second argument in the + trace operations, e.g. + </para> + +<programlisting><![CDATA[ + public static void traceEntry(String str, Object obj); + public static void traceExit(String str, Object obj); +]]></programlisting> + + <para> + In this case, the caller is still responsible for sending the right + object, but at least there is some guarantees that some object will be + passed. The calls will look like: + </para> + +<programlisting><![CDATA[ + Trace.traceEntry("Square.distance", this); +]]></programlisting> + + <para> + In either case, this change to the requirements of tracing will have + dramatic consequences in the rest of the code -- every call to the + trace operations traceEntry and traceExit must be changed! + </para> + + <para> + Here's another advantage of doing tracing with an aspect. We've already + seen that in version 2 <literal>traceEntry</literal> and + <literal>traceExit</literal> are not publicly exposed. So changing + their interfaces, or the way they are used, has only a small effect + inside the <classname>Trace</classname> class. Here's a partial view at + the implementation of <classname>Trace</classname>, version 3. The + differences with respect to version 2 are stressed in the + comments: + </para> + +<programlisting><![CDATA[ +abstract aspect Trace { + + public static int TRACELEVEL = 0; + protected static PrintStream stream = null; + protected static int callDepth = 0; + + public static void initStream(PrintStream s) { + stream = s; + } + + protected static void traceEntry(String str, Object o) { + if (TRACELEVEL == 0) return; + if (TRACELEVEL == 2) callDepth++; + printEntering(str + ": " + o.toString()); + } + + protected static void traceExit(String str, Object o) { + if (TRACELEVEL == 0) return; + printExiting(str + ": " + o.toString()); + if (TRACELEVEL == 2) callDepth--; + } + + private static void printEntering(String str) { + printIndent(); + stream.println("Entering " + str); + } + + private static void printExiting(String str) { + printIndent(); + stream.println("Exiting " + str); + } + + + private static void printIndent() { + for (int i = 0; i < callDepth; i++) + stream.print(" "); + } + + + abstract pointcut myClass(Object obj); + + pointcut myConstructor(Object obj): myClass(obj) && execution(new(..)); + pointcut myMethod(Object obj): myClass(obj) && + execution(* *(..)) && !execution(String toString()); + + before(Object obj): myConstructor(obj) { + traceEntry("" + thisJoinPointStaticPart.getSignature(), obj); + } + after(Object obj): myConstructor(obj) { + traceExit("" + thisJoinPointStaticPart.getSignature(), obj); + } + + before(Object obj): myMethod(obj) { + traceEntry("" + thisJoinPointStaticPart.getSignature(), obj); + } + after(Object obj): myMethod(obj) { + traceExit("" + thisJoinPointStaticPart.getSignature(), obj); + } +} +]]></programlisting> + + <para> + As you can see, we decided to apply the first design by preserving the + interface of the methods <literal>traceEntry</literal> and + <literal>traceExit</literal>. But it doesn't matter—we could as + easily have applied the second design (the code in the directory + <filename>examples/tracing/version3</filename> has the second design). + The point is that the effects of this change in the tracing + requirements are limited to the <classname>Trace</classname> aspect + class. + </para> + + <para> + One implementation change worth noticing is the specification of the + pointcuts. They now expose the object. To maintain full consistency + with the behavior of version 2, we should have included tracing for + static methods, by defining another pointcut for static methods and + advising it. We leave that as an exercise. + </para> + + <para> + Moreover, we had to exclude the execution join point of the method + <filename>toString</filename> from the <literal>methods</literal> + pointcut. The problem here is that <literal>toString</literal> is being + called from inside the advice. Therefore if we trace it, we will end + up in an infinite recursion of calls. This is a subtle point, and one + that you must be aware when writing advice. If the advice calls back to + the objects, there is always the possibility of recursion. Keep that in + mind! + </para> + + <para> + In fact, esimply excluding the execution join point may not be enough, + if there are calls to other traced methods within it -- in which case, + the restriction should be + </para> + +<programlisting><![CDATA[ +&& !cflow(execution(String toString())) +]]></programlisting> + + <para> + excluding both the execution of toString methods and all join points + under that execution. + </para> + + <para> + In summary, to implement the change in the tracing requirements we had + to make a couple of changes in the implementation of the + <classname>Trace</classname> aspect class, including changing the + specification of the pointcuts. That's only natural. But the + implementation changes were limited to this aspect. Without aspects, we + would have to change the implementation of every application class. + </para> + + <para> + Finally, to run this version of tracing, go to the directory + <filename>examples</filename> and type: + </para> + +<programlisting><![CDATA[ +ajc -argfile tracing/tracev3.lst +]]></programlisting> + + <para> + The file tracev3.lst lists the application classes as well as this + version of the files <filename>Trace.java</filename> and + <filename>TraceMyClasses.java</filename>. To run the program, type + </para> + +<programlisting><![CDATA[ +java tracing.version3.TraceMyClasses +]]></programlisting> + + <para>The output should be:</para> + +<programlisting><![CDATA[ + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Circle(double, double, double) + <-- tracing.Circle(double, double, double) + --> tracing.Circle(double) + <-- tracing.Circle(double) + --> tracing.TwoDShape(double, double) + <-- tracing.TwoDShape(double, double) + --> tracing.Square(double, double, double) + <-- tracing.Square(double, double, double) + --> tracing.Square(double, double) + <-- tracing.Square(double, double) + --> double tracing.Circle.perimeter() + <-- double tracing.Circle.perimeter() +c1.perimeter() = 12.566370614359172 + --> double tracing.Circle.area() + <-- double tracing.Circle.area() +c1.area() = 12.566370614359172 + --> double tracing.Square.perimeter() + <-- double tracing.Square.perimeter() +s1.perimeter() = 4.0 + --> double tracing.Square.area() + <-- double tracing.Square.area() +s1.area() = 1.0 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +c2.distance(c1) = 4.242640687119285 + --> double tracing.TwoDShape.distance(TwoDShape) + --> double tracing.TwoDShape.getX() + <-- double tracing.TwoDShape.getX() + --> double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.getY() + <-- double tracing.TwoDShape.distance(TwoDShape) +s1.distance(c1) = 2.23606797749979 + --> String tracing.Square.toString() + --> String tracing.TwoDShape.toString() + <-- String tracing.TwoDShape.toString() + <-- String tracing.Square.toString() +s1.toString(): Square side = 1.0 @ (1.0, 2.0) +]]></programlisting> + + </sect3> + </sect2> + </sect1> +</chapter> + +<!-- +Local variables: +compile-command: "java sax.SAXCount -v progguide.xml && java com.icl.saxon.StyleSheet -w0 progguide.xml progguide.html.xsl" +fill-column: 79 +sgml-local-ecat-files: "progguide.ced" +sgml-parent-document:("progguide.xml" "book" "chapter") +End: +--> diff --git a/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/two.properties b/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/two.properties new file mode 100644 index 000000000..2aaac9a85 --- /dev/null +++ b/taskdefs/testdata/incTest/injarSrc/two/twoSubdir/two.properties @@ -0,0 +1 @@ +hel=lo diff --git a/taskdefs/testdata/incTest/readme-incTest.html b/taskdefs/testdata/incTest/readme-incTest.html new file mode 100644 index 000000000..70dbd9a37 --- /dev/null +++ b/taskdefs/testdata/incTest/readme-incTest.html @@ -0,0 +1,97 @@ +<html> +<title>Incremental test of AjcTask Ant task</title> +<body> +<h2>Incremental test of AjcTask Ant task</h2> +This directory contains files for manually testing the +incremental behavior of the AjcTask Ant task. +It has an Ant script which builds two input jars +and runs an incremental test using a tag file. +<p> +The results must be verified by hand manually at present; +This test should be updated to run automatically. + +<h3>Sample procedure</h3> +This shows how to set up the test and check whether +the incremental tag file and injar-copying features work. +Throughout, <code>{ant}</code> is assumed to be +<code>../../../lib/ant/bin/ant</code> and the directory +is assumed to be this directory, with the Ant build script +<a href="incTest.xml">incTest.xml</a>. +The script uses the eclipse classpath rather than aspectjtools.jar, +so that updates to the sources can be tested without +rebuilding the distribution. + +<ol> +<li>Set up the output jars +<pre> {ant} -f incTest.xml setup</pre> + This should produce <code>one.jar</code> and <code>two.jar</code> + in the directory <a href="injars">injars/</a>. + <p> + </li> + +<li>Start the run with the default settings + (use <code>output/tagfile.txt</code> as the tag file + and to include the non-.class contents of the input jars + in the output jar): +<pre> {ant} -f incTest.xml test</pre> +The task will do an initial compilation and silently wait. + <p> + </li> +<li>In another shell, test the classes in the generated output jar: +<pre> export CLASSPATH="../../../lib/test/aspectjrt.jar;output/outjar.jar" + java packageOne.Main</pre> + Inspect the files in the <a href="src">src/</a> and + <a href="injarSrc">injarSrc/</a> directories to find other + main classes and to determine the expected result of the aspect. + As of this writing, + the aspect <a href="src/TraceMains.java">src/TraceMains.java</a> + should emit messages before and after + the execution of any static main(String[]) method. + <p> + </li> +<li>Also test that the injar files were copied to the output jar. + The non-.class files in <a href="injarSrc">injarSrc/</a> + should be included in the output jar: +<pre> jar tf output/outjar.jar</pre> + <p> + </li> +<li>To test incremental compiles, + edit the aspect <a href="src/TraceMains.java">src/TraceMains.java</a> + to modify the behavior. You can also modify the input jars or + source files. + Then touch or edit <a href="output/tagfile.txt">output/tagfile.txt</a> + to provoke recompilation. + <p> + </li> +<li>Test the generated output jar again + to see if the changes are reflected in the runtime behavior + and if the non-.class files are being copied correctly. + <p> + </li> +<li>Repeat this process until satisfied. Delete the tagfile to quit: +<pre> rm output/tagfile.txt</pre> + <p> + </li> +</ol> + +To test the <code>-incremental</code> option or to test without +copying input jar files or using an output jar, edit the +<a href="incTest.xml">incTest.xml</a> script and redo the test. +<p> + +<h3>Clean-up</h3> +Take care to preserve CVS state after running this test. +It calls on you to edit checked-in files and generates files +to local repository directories. The best thing is to delete +the <code>output</code> and <code>injars</code> directories +and revert to repository forms for sources (unless fixing them). + +<h3>Upgrade</h3> +This needs to be upgraded to run manually. +There is a file/tree-comparison utility to revive from the +aspectj-attic which can be used to compare expected and actual +results of the jar files. Setting up the tasks to run incrementally +might be done using the parallel task, but... +<hr> +</body> +</html> diff --git a/taskdefs/testdata/incTest/src/TraceMains.java b/taskdefs/testdata/incTest/src/TraceMains.java new file mode 100644 index 000000000..acf94be57 --- /dev/null +++ b/taskdefs/testdata/incTest/src/TraceMains.java @@ -0,0 +1,14 @@ + +public aspect TraceMains { + + + before() : execution(static void main(String[])) { + String m = thisJoinPointStaticPart.getSignature().toString(); + System.out.println("before " + m); + } + after() returning: execution(static void main(String[])) { + String m = thisJoinPointStaticPart.getSignature().toString(); + System.out.println("after " + m); + } + +}
\ No newline at end of file diff --git a/taskdefs/testdata/incTest/src/packageOne/Main.java b/taskdefs/testdata/incTest/src/packageOne/Main.java new file mode 100644 index 000000000..278614543 --- /dev/null +++ b/taskdefs/testdata/incTest/src/packageOne/Main.java @@ -0,0 +1,8 @@ + +package packageOne; + +public class Main { + public static void main(String[] args) { + System.out.println("in packageOne.Main.main(..)"); + } +}
\ No newline at end of file |