object evolution a chapter from the research work on applying aspect-oriented software development...
DESCRIPTION
Three Kinds of Evolution I-Evolution: Moving down the inheritance tree. M-Evolution: Evolving by applying mixins. S-Evolution: Evolving by applying shakeins. Overview Motivation I-Evolution M-Evolution S-EvolutionTRANSCRIPT
Object Evolution A chapter from the research work on
Applying Aspect-Oriented Software Development to Middleware Frameworks
Tal Cohen
Research supervisor: Yossi Gil
Object Evolution• Dynamic reclassification: allowing an object to change
its type at runtime.– e.g., prince → frog.– Supported by Smalltalk, several others.– Many real-world uses
• e.g., State design pattern.
• Type safety problems…Prince p = new Prince();if (…) p → Frog();p.drawSword(); // potential runtime type error
• Our solution: limit to monotonic changes only.– "Object evolution" -- moving down the inheritance tree.– Prince → king is okay!
begin::Overview
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Three Kinds of Evolution
• I-Evolution: Moving down the inheritance tree.
• M-Evolution: Evolving by applying mixins.
• S-Evolution: Evolving by applying shakeins.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Contributions
• The case for object evolution.– Why you want your language to support this feature.
• Concrete language extension.– The idea is simple, the details are not trivial.
• Three kinds of evolution.– A language can support any one, two, or all.
• Analysis of potential failures.– What can go wrong, and how to cope with it.
• Implementation strategies.– The nitty-gritty details.
end::
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Motivation
The State Design Pattern
begin::Motivation
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
State Example: TCPConnection
• TCPConnection responds differently to messages (open, close, etc.) depending on its state.
• Possible states: Listen, Established, Close.
• A switch statement in each method…!?
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
TCPConnection using State
• Two objects in memory• Repeated delegation code• API must be identical
– No state-specific public methods.
TCPConnectionopen()close()acknowledge()
TCPStateopen()close()acknowledge()
state
TCPEstablishedopen()close()acknowledge()
TCPListenopen()close()acknowledge()
TCPClosedopen()close()acknowledge()
state.open();
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Knowledge Refinement
• State pattern not applicable: subclass has many additional methods.– Should the "changeable" class contain the union of all
methods of all possible states!?• Creating a new object implies:
– Copying existing state (fields),– Chasing references.
MethodInvocation
ObjectRefx
MethodNameequals
ParamsList(y)
x.equals(y)Parsing the source line:
InterfaceMethodInvocation
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Lazy Data Structures
• A specific case of knowledge refinement.
• Example: XML/HTML parsing.– "Varying interface" problem particularly
acute.• Should the "Node" superclass include the
features of every possible node type?• 80 different node types in XHTML…
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Data Covarianceclass LinkedList { static class Node { Node next; Object data; … constructor not shown … }
Node head, tail;
void append(Object obj) { Node newNode = new Node(obj); … add newNode at end of list … }
class BidiLinkedList extends LinkedList { static class BidiNode extends Node { Node prev; … new constructor not shown … }
void append(Object obj) { BidiNode newNode = new BidiNode(obj); … add newNode at end of list … }
•Because each class needs a different node type, we can't use refinement.
•The result: code duplication.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Dynamic Shakeins
• Shakeins are a superior alternative to AspectJ-style aspects.
• Newer AOP languages support dynamic aspects.– Aspects can be applied/removed at runtime.
• Applying/removing a shakein at runtime implies changing the object's class!
• This was the original motivation for this work.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Dynamic Shakeins
• For example: given an object of type Account, we wish to enable logging.– From Account to Logging<Account>.– … and back, as needed.
• Another example: protect a List object by applying the ReadOnly shakein to it.– Turning it into ReadOnly<List> without
changing the list content.
end::
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
I-Evolution
I-Evolution
• Move an object down the inheritance tree.
• Syntax: v → T(…)– v is an object reference.– T is a type.
• Must be a subtype of v's static type.– … indicates optional parameters.– → can be typed as ->.
begin::I-Evolution
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
I-Evolution Example 1
• Note that connection objects cannot be "recycled"
–No way to reverse state.–Solution: later…
class TCPConnection { // Listen state public void open() { … establish connection … this → TCPConnectionEstablished(); } …}
class TCPConnectionEstablished extends TCPConnection { public void close() { … close connection … this → TCPConnectionClosed(); } …} class TCPConnectionClosed
extends TCPConnectionEstablished { public void close() { throw new IllegalStateException(); } …}
• No need for method delegation.
• Only one object in memory.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
I-Evolution Example 2class LinkedList { static class Node { Node next; Object data; … constructor not shown … }
Node head, tail;
void append(Object obj) { Node newNode = new Node(obj); … add newNode at end of list … }
class BidiLinkedList extends LinkedList { static class BidiNode extends Node { Node prev; … new constructor not shown … }
void append(Object obj) { BidiNode oldTail = (BidiNode) tail; super.append(obj); tail → BidiNode(); tail.prev = oldTail; }
•BidiLinkedList.append is now a refinement of the inherited version.
•No code duplication!
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Evolvers
• The change from class T to subclass T' might require code execution.
• Required for preserving class invariants.
• For example: when evolving from LinkedList to BidiLinkedList, we must go over the list and turn each Node into a BidiNode.– And instate the prev pointer while doing so.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Evolver Example• The evolver
represents the "delta" between the superclass constructor and this class's constructor.
• However, it must take into account the possibility that the object is in a non-virgin state.
class BidiLinkedList extends LinkedList { static class BidiNode extends Node { Node prev; … }
public →BidiLinkedList() { // Evolver if (head == null) return; head → BidiNode(); BidiNode current = head; while (current.next != null) { current.next → BidiNode(); current.next.prev = current; current = current.next; } …
• If no evolver is specified, default evolvers can often be deduced.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
How Can I-Evolution Fail?
• I-Evolution never introduces runtime type errors.– i.e., when an object accepts a message, it
knows how to handle it.• Yet the evolution operation itself can fail.
• Failure #1: v → T(…), v is null.– Not an "empty operation" because we might
expect the evolver code to do something.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
How Can I-Evolution Fail?
• Failure #2: v → T(…), and the evolver code →T throws an exception.– Just as new can fail due to exceptions.
• Failure #3: v → T(…), but v's dynamic type is not a superclass of T.– Given:
– This might fail:
A
B C
void foo(A a) { a → B(); // But what if a instanceof C!?}
Note: This is just like the downcast operation (B)a can fail!
end::
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
M-Evolution
Mixin Example
• This mixin can be applied to any class that implements the List interface:
begin::M-E
volution
mixin ReadOnly { inherited public void add(Object o); inherited public void remove(int index);
public final void add(Object o) { throw new IllegalStateException(); }
public final void remove(int index) { throw new IllegalStateException(); }}
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Is "ReadOnly" Useful?
• We can now create objects of type, e.g., ReadOnly<Vector>.
• However, even the constructor of this class will fail to add items to it!
• The mixin looks useless, unless we can turn existing list objects into read-only.
• … Evolution!
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Evolving to ReadOnly
• ReadOnly<Vector> is a new class, which extends Vector.
• So we can use regular I-Evolution with it.• But a reference to List can actually be of
type Vector, ArrayList, LinkedList, or any other implementation class!
if (lst instanceof Vector) lst → ReadOnly<Vector>();else if (lst instanceof ArrayList) lst → ReadOnly<ArrayList>();else … unbounded number of possibilities …
I-Evolution doesn't cut it in this case.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
M-Evolution
• Syntax: v → M<v>(…)– v is an object reference.– M is a mixin.– v is evolved into the class created by
applying M to v's dynamic type!
lst → ReadOnly<lst>();… works for any implementation of List! …
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Idempotent Mixins
• M-Evolution can result in infinitely long inheritance threads, generated at runtime.– ReadOnly<ReadOnly<ReadOnly<Vector>>>
• To prevent this, if mixin M is marked @Idempotent (or detected to be idempotent), then applying M to M<T> yields M<T>.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
How Can M-Evolution Fail?
• Existing failures: Evolving null, exception-throwing evolvers.
• Avoided failures: never fails due to incorrect dynamic type!
• New failure type: inability to apply mixin.– Final class / final members.– Accidental overriding.
end::
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
S-Evolution
What is a Shakein?
• A Shakein makes a re-implementation of a class.
• It does not change the type.– We get a new implementation of an existing
type.– Never introduces new non-private members.
• A parameterized, generic-like structure.
begin::S-E
volutionO
verviewM
otivationI-E
volutionM
-Evolution
S-E
volution
Shakein Example
shakein Logging { before execution of public methods { Log.log("Began " + methodName); }
after successful execution of same { Log.log(methodName + " ended successfully"); }
after failed execution of same { Log.log(methodName + " caused exception: " + e); }}
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
S-Evolution
• Syntax: v → S<v>(…)– v is an object reference.– S is a shakein.– v is evolved into the class created by
applying S to v's dynamic type!
obj → Logging<obj>();… works for just about any object! …
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Replacing One Shakein by Another
• S-Evolution does not change the object's type.– Only it's (implementation) class.
• It is therefore possible to undo S-Evolution without any risk for runtime type errors.
• Or, replace one shakein with another.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
State Groups
• A set of shakeins can be declared as belonging to the same state group.
• If S1, S2 S, then:– State transition: Applying S1 to S2<T> yields S1<T>.
• And vice versa.
– All states are idempotent: Applying S1 to S1<T> yields S1<T>.
• Likewise for S2.
• i.e., an object can only have one shakein (state) from a given state group.
• Note: idempotent mixins can be viewed as one-state groups.
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
State Group Exampleclass TCPConnection { // Default = listen state public void open() { … establish connection … this → Established<this>(); } …}
@StateGroup("Connection")shakein Established { public void close() { … close connection … this → Closed<this>(); } …}
@StateGroup("Connection")shakein Closed { public void reset() { this -> Listen<this>(); } …}
• Connection objects can now be recycled.
@StateGroup("Connection")shakein Listen { // Empty: restore default state}
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Dynamic Aspects with Shakeins@StateGroup("Log")shakein Logging { before execution of public methods { Log.log("Began " + methodName); }
after successful execution of same { Log.log(methodName + " ended successfully"); }
after failed execution of same { Log.log(methodName + " caused exception: " + e); }}
@StateGroup("Log")shakein NotLogging {
// Empty: restore default}
obj → Logging<obj>();…
obj → NotLogging<obj>();
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
• Unlike some other dynamic aspect solutions:
– No boolean test at every method call. – No list of aspects to iterate over.– Zero performance overhead when no
shakeins are applied.• However, applying/removing a dynamic
aspect (i.e., evolution) can be costly.– Depending on implementation strategy.– We expect this operation to be less
common than method invocation.
How Can S-Evolution Fail?
• Existing failures: Evolving null, exception-throwing evolvers.
• Applying to final class / final members.• Avoided failures: never fails due to
incorrect dynamic type!• Never fails due to accidental
overriding!– Since no new members are introduced.
end::
Overview
Motivation
I-Evolution
M-E
volutionS
-Evolution
Summary
Summary
• The quest for dynamic shakeins was successful.
• … with several extra benefits.• Language designers can choose any
subset of {I,M,S}-Evolution.• Some issues not discussed here:
– The fine details of evolvers.– Multi-state objects.– Implementation strategies.