Welcome to Apostle, an extension providing Aspect-Oriented Programming (AOP) functionality to the Smalltalk language. This work is the basis of my master's thesis at the University of British Columbia, being done under the supervision of Gregor Kiczales. It's currently a mostly-faithful `port' of AspectJ to Smalltalk, sporting a similar join point model, aspects and advice.
This pre-release is being done to get feedback and direction of the language and system; being so new, much of Apostle is either subject to change or undefined. Thus this document is filled with notes of the form:
[TBI: ...]indicating points To Be Investigated. Any comments you may have relating to these would be appreciated. Actually, any comments are appreciated! Please direct them to me.
Apostle was previously called the yawn-inducing Aspect/Smalltalk. This old name is still very present, especially in the source code. This will change over time.
Apostle is currently only available for IBM's VisualAge for Smalltalk 4.5. Why? Because neither the ideas nor the engineering in this pre-release are considered stable enough to consider porting it elsewhere. It may `just work' under VAST 5.5, but I haven't tried it. I expect to upgrade to 5.5 at some point, and I'd especially like to see it ported to Squeak.
This release is available at http://www.cs.ubc.ca/labs/spl/projects/apostle/v0.2/apostle-0.2.tar.gz
Be sure to check the patches section below; some are required for successful operation of Apostle.
Please notice this pre-release's very styling super-mega-alpha2 designation. Realize that this is an extremely young piece of software. It is also a very powerful tool: small changes can have very large effects. To reiterate:
This software has and provides great potential for destroying your imageThis cannot be emphasised enough: this software rewrites many components of the image. While it behaves in a predictable fashion, it can be applied in ways not imagined by the user. Consider the following piece of advice as an example:
around<receptions(#at:)> self hasCachedContents ifFalse: [aspect refreshCacheFor: self]. PROCEEDThis example has two major problems:
#at:
; this is effectively all types, as
#at:
is defined in Object
. Do all types
understand hasCachedContent
? If not, this will
cause walkbacks: this can be devastating since this will be
applied to almost all Collection classes.
before
advice.
That said, I've been using it live for the last couple of days without a hitch. You can disable it with the following doit:
AspectSmalltalk disableand remove all applied aspects/advice with: with the following
AsLoom current reset
The TODO section below has more detail on missing or buggy functionality. Please remember that this isn't real yet: play with the software, look at the examples (however trivial), try something. But please don't rely on it yet.
All of this code is copyrighted in some form or another.
The SUnit*
application is copyrighted by its respective
owners.
The MtTools
and MtToolsTests
applications were
previously written by me under contract, and are copyrighted 2000-2001
by Brian de Alwis. They were copyrighted under a BSD-like license, but
as I've misplaced my copyright notice, I'll make release these for
now with all rights reserved. If you'd like to use them,
e-mail me; I'll re-release them
with a different copyright and even with documentation!
The remaining code is copyright by the University of British Columbia, as dictated by UBC's Policy 88 on Patents and Licensing. Again, all rights are reserved: you may use the code for evaluation of this software.
Phew!
If Apostle is still very much in its infancy (and it is), then this documentation is even more so. But Apostle's being based on AspectJ means that it can leverage from AspectJ's documentation too. Recommended in particular are:
This document includes some brief notes on the Apostle language. But I recommend that you UTSL too (Use the Source, Luke!). The implementation section below describes the structure of the implementation. Also included is a lightly aspectified application; see the notes below on using the simulation.
Apostle is very new and very open to change. While based on the model of AspectJ, it can (and is expected to) diverge. This pre-release is being done to get feedback and direction of the language.
Having read the AspectJ documentation (you have read them, right?), the remainder of this section presents the details of the Apostle language.
Aspects are created as subclasses of the AsAspect
class, using a similar protocol as that for creating classes:
<aspectClass> subaspect: 'aspectName' instanceVariableNames: 'inst var names ' classVariableNames: 'class var names' poolDictionaries: 'pool names'Or
#subaspect:classInstanceVariableNames:...
to declare
class-instance variables. [TBI: These messages should not be
considered fixed, as they must somehow change to incorporate the
aspect's type: singleton, per-object, cflow, etc. Plus, should they
be called aspectVariableNames:
or
aspectInstanceVariableNames:
?]
Currently only `singleton' aspects are supported. Singleton aspects have only a single aspect instance in the system at any time. These were called of eachJVM in AspectJ, and have now become the default. Aspect instances are explained in the next section.
Aspect instances are created upon some condition, depending on the
aspect-type; they cannot be created explicitly. The instances are
managed automatically, being released when appropriate. The aspect
instance for a particular object can be obtained with the
AsAspect class>>#aspectFor:
message.
[TBI: Should there be shortcuts for singleton aspects? Or
should they use
<aspectClass> aspectFor: nil
?]
Join points identify points in the execution of the program. They are identified by pointcut designators, declared and saved just like methods. They can be defined either on classes or aspects. Pointcuts have the following format:
pointcut name(): pointcut-expressionpointcut-expressions are similar to those in AspectJ: join points, named pointcuts, or sub-expressions connected by the set operators `&&' or `||', or complemented with `!'. The currently defined join points are:
Application
s or SubApplication
s;
a class's namespace is considered to be its defining application
(found by executing the doit:
`<class> controller
').
[TBI: This may strike the reader as strange as it may seem
more logical to have `kindOf(CLIM.Bag)
' identify the
methods of Bag from its extension in the application CLIM
.
But kindOf() identifies types, not methods; besides
which, implementing this behaviour this is difficult!]
Class methods are identified by providing the class
modifier.#doesNotUnderstand:
.
kindOf(Foo) && receptions(#hash) && cflow(kindOf(Bag) && (receptions(#add*) || receptions(#remove*)))identifies all join points where
#hash
is sent to an
instance of Foo
as a result of being added to
or removed from a Bag
.
kindOf(Bag) && sends(#hash)becomes
receptions(#hash) && cflow(kindOf(Bag))[TBI: This may be a better overall solution anyways; sends() cannot be used with constructed selectors, and there's no way of narrowing it for a particular type, since kindOf() only applies against the receiver...]
Named pointcuts can be referenced from other pointcuts. For example:
pointcut foo(): kindOf(Aardvark) && receptions(add:) pointcut bar(): cflow(foo()) && kindOf(Block) && receptions(value*)Currently there is only a single-level namespace for pointcuts; this is expected to change [TBI: Automatically incorporate namespace and class names? So
foo()
defined in CLDT.Bag
becomes CLDT.Bag.foo()
?]
Pointcuts cannot currently capture arguments; support for this is forthcoming.
Advice attaches code to identified join points; it annotates the execution of the program. Similar to pointcuts, advice is declared and defined in a browser; unlike pointcuts, advice can only be defined on aspects, and must always be defined as instance methods.
Advice is of the form:<advice-specification> <advice-body>There are currently three forms of advice-specification:
PROCEED
keyword; by omitting it, it is possible to prevent executing the
original point. The return-value of the execution point is taken to
be the return-value of the advice-body.
Advice code executes in a rather different context than might be
expected, and is very different from that in AspectJ.
self
identified the executing object instance of the
execution context (as opposed to AspectJ's this
being the
aspect instance), but inst-var accesses are in the context of the
aspect instance. Methods on the aspect can be sent to aspect
.
I believe this makes sense as I view the aspect as
extending the class, implementing a different concern from that of the
original class. But the aspect expresses a different concern from that
in the class, and thus shouldn't have direct access to the instance's
innards.
[TBI: This is one aspect of Apostle and AOP that is of great
interest. I find that providing a representation of aspect instances as
first-class objects is a difficult concept for users. This is open
to change.]
An example piece of advice:
before<kindOf(Foo) && receptions(add*)> aspect isFull ifTrue: [aspect dropLeastRecentlyUsed]. aspect rememberNewestReceiver: self.
The implementation is grouped as a set of ENVY applications and subapplications:
AspectSmalltalk AspectSmalltalkImplementation AspectSmalltalkPointcutExpressions AspectSmalltalkES AspectSmalltalkTestingAspectSmalltalk contains the classes expected to be `public' (i.e. with some public API). AspectSmalltalkImplementation and AspectSmalltalkPointcutExpressions exist to hide implementation code irrelevant to users. Some effort has been made to isolate ENVY/VAST-specific code into AspectSmalltalkES, to aid in future porting work. Lastly, AspectSmalltalkTesting has some unit-tests built with SUnit, nice debugging printStrings for various Apostle objects, and some helper methods for development.
Features currently working in this pre-release are:
<aspectClass> reset
To enable defining pointcuts from within classes, you'll need to
explicity instruct VAST to use the provided aspect compiler,
AsCompiler
, by:
Compiler := AsCompilerThis is reasonably safe: I've been running live with this for weeks now.
To define pointcut objects, use the AsPointcutCompiler
. You
can combine pointcuts with the `&&
' and
`||
' protocols, and with the #not
method. For
example:
| pcut1 pcut2 | pcut1 := AsPointcutCompiler compile: 'kindOf(Foo)'. pcut2 := AsPointcutCompiler compile: 'kindOf(Bar)'. ^pcut1 || pcut2
Included is a lightly aspectified application. This simulation was written to demonstrate that the performance of a system made up of asynchronous elements may actually be determined by the time of its slowest operations due to a phenomenon called percolation. This is discusses in more detail in:
Mark Greenstreet, Brian de Alwis. How to Achieve Worst-Case Performance, accepted to the Seventh International Symposium on Asynchronous Circuits and Systems (ASYNC 2001), Salt Lake City, USA. (pdf)
The simulation is split into two ENVY applications: PercolationSimulation embodied the simulation model and logic, and PercolationSimulationGraphing provided the graphical presentation. The start the simulation, use the following doit:
SimulationWindow new openand use Simulation -> New Simulation to start a new simulation.
There is currently only one aspect in the simulation:
#literalSize
Apostle does a lot of source-rewriting and method-manipulation, and
thus uses CompiledMethod>>#setSource:
quite
extensively. This seems to trigger a bug in ENVY/Manager in VAST 4.5,
specifically ClassDescription>>rebindVariablesIn:using:
.
Replace the bolded code in the following, found near the beginning of
the method:
1 to: methodOrCopy size do: [:index |with
1 to: methodOrCopy literalSize do: [:index |This may have been fixed in VAST 5.5.