Aspectual Collaborations
Flexible Modules for
Generic Object Oriented
Programming
Host Class Graph
• Consists of Classes written by programmer
connected by interrelationships
• Has-A parts
• Is-A edges
• Just plain ol’ java.
Collaboration
• Like a class graph, but
– Roles instead of Classes
– Roles may have missing behavior (methods or
fields)
– Closed
• No new roles can be added to the collaboration
Roles
• Differ from classes in that they may have
missing methods or fields
• Different from abstract
– Abstract behavior must be method
– Abstract classes can never be instantiated
– Roles will be completed in-situ at a later date.
Adapter
• Connects host class graph and collaboration
– Classes and Roles equated pointwise
– Subject to semantic constraints
• Collaboration is stretched to fit
• Provides expected parts by exports from
host
• Exports some parts of collaboration to
outside world, encapsulates rest
Example: Sort Host
package host;
class HasUsers {
User[] users;
void initUsers() { ... }
void addUser(User u) { ... }
void sortusers() { System.err.println("No sorting. Yet."); }
User firstuser() {
sortusers();
return users[0];
}
}
class User {
String name;
int uid;
boolean alphabeticallybefore(User that) {
return this.name.lexicographicallybefore(that.name);
}
boolean uidbefore(User u) { return this.uid < that.uid; }
}
Example: Sort Collaboration
collaboration sort;
role ArrayHolder {
expected Item[] arr;
void insertionsort() {
for (int i=0; i
if (arr[j].before(arr[i])) swap(i,j);
int m=0.0;
for (int i=0; i
System.out.println("geom. average number of moves: "+(m/arr.length));
}
private void swap(int i, int j) {
arr[i].moved++; arr[j].moved++;
Item o = arr[i]; arr[i]=arr[j]; arr[j]=o;
}
}
role Item {
expect boolean before(Item other);
int moved = 0;
}
Example: Sort Adapter
adapter sortedhost;
combine {sort,host} {
host.HasUsers += sort.ArrayHolder {
sort::Item[] arr provided-by host::User[] users;
host::void sortusers() provided-by sort::void insertionsort();
}
host.User += sort.Item {
sort::boolean before(Item) provided-by host::boolean uidbefore(User);
}
}
Discussion
• Expected parts are what give a collaboration
a tight yet generic coupling to the host
application.
• Allow generic behavior to have complex
interactions with host behavior.
Different class graphs
• How much may the collaboration’s role
graph and host class graph differ?
– Must maintain sub/super class links
– Expected types after mapping must match types
provided by host: Item must be User since
arr is users
Composition
• Why does the host need to be a complete
class graph? Why not a collaboration too?
• Provide behavior needed by one from the
other.
• Some behavior remains expected.
• But, What is the return type?
– come back next week for exciting conclusion!
Same bat time, same bat channel
A Tale of Two Implementations
Dynamic / Proxy
• Layering behavior like a cake
• Host remains unchanged
• Two simultaneous different types, but no object
equality between them.
• Behavior can be removed
• No permanent memory overhead
• Hard to compose
• Object mapping hard (vectors are nigh impossible)
• Unclear whether we can be type safe and pre-
compiled at the same time
‘tis a far far better thing
• Combine bytecode to make new class
– Already front-end processed
– Type checked, easily parsable
– More SOP than layering
• Determine types by adapter
• Permanent
• Can be precompiled type safely (each object
has only one type)
Format of Class Files
• The secret to working
ClassPool
with class files are
their convenient layout
• All references are Info
stored in one local
pool, the class pool. Fields
Parts are stored in area,
Methods
and class attributes in
the last. Attributes
The Constant Pool
• All identifiers and references stored there
– MethodRef, FieldRef, InterfaceMethodRef
– External Method and Type Refs
• same for fields and methods!
– Constant: Strings, Ints, Doubles, Classes, …
– Raw Strings
No need to inspect method body
• To move into a new class, we just do a
recursive copy into new class, chasing all
index pointers into constant pool.
Dealing with Expected Parts
• Preprocessor adds stub body to all expected
method.
• Postprocessor annotates expected parts as such, so
that stub may be thrown away.
• For unit testing purposes, bodies may not be stubs,
but user-written test stubs that simulate expected
behavior.
– Allows collaborations to be tested without application.
– Can be simulated by linking to simplistic app.
How to store annotations
Constant pool
Void foo()
• Expected methods
OtherClass::int bar
need to be marked as
such, so that our 1234.6
implementation knows
it’s ok to remove their Method entry in class
existing body. Info_index
• Class files allow ATTR_expected
ATTR_deprecated
additional attributes to ATTR_Code
be stored on fields and
methods. Byte code is
one
Alpha Conversion vs Export
• Of course, we don’t want to pollute name
space of resulting class file, so we
systematically rename all intra-
collaboration references.
• Names exported are renamed to resulting
name, while non-exported names are
renamed to unpronouncable names ($).
Implementation Subconclusion
• Class files are easy and flexible to work with
• Pre-parsed semantics make our life easier, while
compiled status means we can take well-
formedness for granted.
• This implementation is statically linked (we build
up new collaborations from old statically).
Dynamic linking is an option, but hard.
Composition, Cont
• Under insertive approach, the types are
completely decided by adapter: we are
creating new classes
• Must make sure no expected parts are
unexported – they become unprovidable
Aspects
• We’ve been promising aspectual
programming. Now we get some.
• Aspects are defined as systematic behaviors
that are localised to issue, but distributed
over multiple classes
• Additionally, the rest of the program can be
oblivious to aspects’ existence
(Filman&Friedman).
Precise Aspects
• Precise aspects we almost have already. All
we require is to not throw away old method
body when replacing a method stub. (memo
to self- what happens when we wrap an
expected method?)
• Allows us to play with arguments of
wrapped method
Example: No Duplicates, please
collaboration nodups;
role ElemHolder {
expected Elem[] elems;
expected void addElem(Elem e);
void insertMaybe(Elem e) {
for (int i=0; i
if (elems[i].equals(e)) return;
addElem(e);
}
}
role Elem {
expect int id;
public equals(Object o) { return ((Elem)o).id == id; }
}
Example: No Duplicates, cont
adapter sortednoduphost;
combine {nodups,sortedhost} {
sortedhost.HasUsers += nudups.ElemHolder {
nodups::Elem[] elems provided-by sortedhost::User[] users;
sortedhost::void addUser(User)
replaced-by nodups::void insertMaybe(Elem)
provides nodups::void addElem(Elem);
}
sortedhost.User += nodups.Elem {
export nodups::boolean equals(Object);
}
}
What happens
• When the addUser method is invoked, the
collaboration addElem is invoked instead,
and eventually invokes the expected old
method.
• Notice that the aspect magic is from the
adapter, the collaboration sees it as a normal
method.
Generic Aspects
• Precise aspects are nice, but very limited
– How often can do we know the interface
precisely
• Approach:
– Precompile method to known simple signature
– Wrap with generated code at adaptation time to
convert arguments and return values of
wrapped host method to the known signature
Example: Keep it Sorted
collaboration keepsorted;
role Thing {
expected void sort();
aspectual ReturnVal sortafter(ExpectedMethod e) {
ReturnVal r = e.invoke();
sort();
return r;
}
}
adapter keepsortedhost;
combine {keepsorted,sortedhost} {
sortedhost.HasUsers += keepsorted.Thing {
keepsorted::void sort() provided-by sortedhost::sortusers();
keepsorted::sortafter wraps sortedhost::void initUsers();
}
} and {
sortedhost.HasUsers += keepsorted.Thing {
keepsorted::void sort() provided-by sortedhost::sortusers();
keepsorted::sortafter wraps sortedhost::void addUser(User);
}
}
Discussion: Generated Code
From Collaboration
package keepsorted;
class Thing {
void sort() { /*expected*/ };
ReturnVal$A sortafter(ExpectedMethod$A e) {
/*aspectual*/
ReturnVal$A r = e.invoke();
sort();
return r;
}
}
abstract class ReturnVal$A { }
abstract class ExpectedMethod$A {
abstract ReturnVal$A invoke();
}
Discussion: Generated Code
package keepsortedhost;
From Adapter
class HasUsers {
...
void sortusers() { ... };
ReturnVal$A sortafter$addUser(ExpectedMethod$A e) {
/*aspectual*/
ReturnVal$A r = e.invoke(); sortusers(); return r;
}
void addUser$A(Elem e) { /* original method body, untouched */ }
void addUser(Elem e) { }
ExpectedMethod$A ex = new ExpectedMethod$A$addUser(this,e);
ReturnVal$A$addUser r = (ReturnVal$A$addUser) sortafter$addUser(ex);
return;
}
}
class ReturnVal$A$addUser extends ReturnVal$A { }
class ExpectedMethod$A$addUser extends ExpectedMethod$A {
User arg1; HasUsers self;
ExpectedMethod$A$addUser(HasUsers s, User u) { self=s; arg1=u; }
ReturnVal$A invoke() {
ReturnVal$A$addUser r = new ReturnVal$A$addUser();
self.addUser$A(arg1);
}
}
The wrapping approach
• Handles return values of any type
• Exceptions in a similar manner
• Since each signature will generate different
extracting code (exceptions must be
rethrown, for example) we have to have
subclasses of ReturnVal for each signature.
We then downcast to the known type to
extract info
The dollar signs
• The needed downcast is in the generated
body for addUser. We must make sure this
cast succeeds, as it is not in user code, and
they will not know what caused it.
Causing a casting error
collaboration fail;
role Foo {
RV old; adapter whatever;
aspectual RV meth(EM e) { combine {fail,host} {
ReturnVal r = e.invoke(); host.A += fail.Foo {
ReturnVal o = old; export fail::RV old
old = r; fail::meth wraps host::A get_a();
return (o!=null):o?r; }
} } and {
} host.B += fail.Foo {
export fail::RV old
package host; fail::meth wraps host::void print();
class A { }
A get_a() { return new A(); } } void failit() {
} A anA = new A(); B aB = new B();
class B { aB.print();
void print() { System.err.println("hi"); } // now aB.old has void ReturnVal
} aB.old = anA.old; // so does anA!
A otherA = anA.get_a();
// tries to unwrap void to A
}
Existential Types
• ReturnVal and ExpectedMethod are local to
an attachment of the collaboration; we
cannot export them.
• We assure this by adding the
unpronounceable $ to their types; thus they
cannot be mentioned in an adapter, and
cannot be exported.
• Casting is still an issue, but that’s java.
Aspectual Subconclusion
• Small addition to collaborations make them
intuitive fits for aspectual programming.
• We are able to offer type-safe separate
compilation of aspects that can be used with
a wide variety of class graphs.
Scoping Collaborations
• Objects have variables scoped per instance and per
class.
• Collaborations have another dimension:
– Per Attachment
• A counter for method invocation is per method. The default.
– Per Host
• A counter for method invocation is shared for methods of
class. Expected parts have this property.
– Per Application
• All the methods of the application share the same counter. Can
be simulated a-la object oriented globals.
A Scoping example
collaboration c_attachment; collaboration c_shared;
role Counter { role CountHolder {
int att=0; int shared=0;
static int st_att=0; static int st_shared=0;
expected int sha; }
expected int st_sha;
aspectual ReturnVal meth(ExpectedMethod exmeth) {
att++; st_att++; sh++; st_sh++;
System.err.println("this wrapped method on this object: "+att+
" and for all objects of this class: "+st_att);
System.err.println("all wrapped methods of this object: "+sha+
" and for all objects of this class: "+st_sha);
return exmeth.invoke();
}
}
adapter counting;
combine {c_shared,c_attachment} {
Counted = {c_shared.CountHolder,c_attachment.Counter} {
c_attachment::int sha provided-by c_shared::int shared;
c_attachment::static int st_sha provided-by c_shared::static int st_shared;
export c_attachment::aspectual meth;
}
}
Discussion: Scoping
• Composition is used to add the expected
parts to the host.
• It is still unclear how we specify that one
sub-collaboration is only added once, the
other possibly multiple times.
Conclusion
• Very flexible system
• Our mechanisms expressive enough to
express many kinds of aspectual behavior
• Few unclear issues
– What is added once from composed collabs,
what is duplicated.
– Wrapping expected methods with ascpectual
methods.