Aspectual Components Part 2 April 5 1999 Composition

  • Slides: 69
Download presentation
Aspectual Components Part 2 April 5, 1999

Aspectual Components Part 2 April 5, 1999

Composition example • Use three aspects simultaneously with three classes. • Three aspects: –

Composition example • Use three aspects simultaneously with three classes. • Three aspects: – Show. Read. Write. Access – Instance. Logging – Auto. Reset • Three classes: Point, Line, Rectangle

Auto. Reset Shapes (Point, Line, Rectangle) Weaved Code Show. Read. Write. Access Point Line

Auto. Reset Shapes (Point, Line, Rectangle) Weaved Code Show. Read. Write. Access Point Line Instance. Logging Rectangle

Inheritance between components component Show. Read. Write. Access extends Show. Read. Access { participant

Inheritance between components component Show. Read. Write. Access extends Show. Read. Access { participant Data. To. Access { expect void write. Op(Object[] args); replace void write. Op(Object[] args){ System. out. println( "Write access on " + this. to. String()); expected(args); }} }

Instance. Logging component (first part) component Instance. Logging { participant Data. To. Log {

Instance. Logging component (first part) component Instance. Logging { participant Data. To. Log { expect public Data. To. Log(Object[] args); replace public Data. To. Log(Object[] args) { expected(args); long time = System. current. Time. Millis(); try { String class = this. class. get. Name() + " "; log. Object. write. Bytes(""New instance of " + class + at "" " + time + "" " n"); } catch (IOException e) {System. out. println(e. to. String()); } } }

Instance. Logging component (second part) protected Data. Output. Stream log. Object = null; public

Instance. Logging component (second part) protected Data. Output. Stream log. Object = null; public init() { try {log. Object = new Data. Output. Stream( new File. Output. Stream(log)); } catch (IOException e) {System. out. println(e. to. String()); } } }

Auto. Reset component Auto. Reset { participant Data. To. Reset { expect void set.

Auto. Reset component Auto. Reset { participant Data. To. Reset { expect void set. Op(Object[] args); expect void reset(); protected int count = 0; replace void set. Op(Object[] args) { if ( ++count >= 100 ) { expected(args); count = 0; reset(); }} } }

Composition of components connector Composition. Conn 1 { {Line, Point} is Show. Read. Write.

Composition of components connector Composition. Conn 1 { {Line, Point} is Show. Read. Write. Access. Data. To. Access with { read. Op = get*; write. Op = set*; }; Point is Auto. Reset. Data. To. Reset with { set. Op = set*; void reset() { x = 0; y = 0; } }; {Line, Point, Rectangle} is Instance. Logging. Data. To. Log; }

Auto. Reset Shapes Weaved Code Show. Read. Write. Accesses Point Line Instance. Logging Rectangle

Auto. Reset Shapes Weaved Code Show. Read. Write. Accesses Point Line Instance. Logging Rectangle

Composition of components Connector graph Composition. Conn 1 Line, Point, Rectangle Show. Read. Write.

Composition of components Connector graph Composition. Conn 1 Line, Point, Rectangle Show. Read. Write. Access. Data. To. Access * * Auto. Reset. Data. To. Reset * Instance. Logging. Data. To. Log * * *

Modified composition connector Composition. Conn 2 extends Composition. Conn 1 { Line is Auto.

Modified composition connector Composition. Conn 2 extends Composition. Conn 1 { Line is Auto. Reset. Data. To. Reset with { set. Op = set*; void reset() {init(); } }; }

Composition of components Connector graph Composition. Conn 1 Line, Point, Rectangle Show. Read. Write.

Composition of components Connector graph Composition. Conn 1 Line, Point, Rectangle Show. Read. Write. Access. Data. To. Access * * Auto. Reset. Data. To. Reset * Instance. Logging. Data. To. Log * * * Connector graph Composition. Conn 2 Line, Point, Rectangle Show. Read. Write. Access. Data. To. Access * * Auto. Reset. Data. To. Reset * * Instance. Logging. Data. To. Log * * *

Modify existing connection statements connector Composition. Conn 3 extends Composition. Conn 1 { Point

Modify existing connection statements connector Composition. Conn 3 extends Composition. Conn 1 { Point is Auto. Reset. Data. To. Reset with { { set. Op = set; void reset() { x = 0; y = 0; }} { set. Op = set. X; void reset() { x = 0; }} { set. Op = set. Y; void reset() { y = 0; }} }; }

Composition of components Connector graph Composition. Conn 3 Line, Point, Rectangle Show. Read. Write.

Composition of components Connector graph Composition. Conn 3 Line, Point, Rectangle Show. Read. Write. Access. Data. To. Access * * Auto. Reset. Data. To. Reset *** Instance. Logging. Data. To. Log * * * overridden: ***

New example: Feature-oriented Programming • Dependent aspects • Order of deployment is relevant

New example: Feature-oriented Programming • Dependent aspects • Order of deployment is relevant

Data. With. Counter component pairwise interaction Data/Counter component Data. With. Counter { private participant

Data. With. Counter component pairwise interaction Data/Counter component Data. With. Counter { private participant Counter { int i=0; void reset(){i=0; }; void inc(){…}; void dec(){…}; } participant Data. Structure { protected Counter counter; expect void init. Counter(); expect void make_empty(); expect void push(Object a); expect void pop(); replace void make_empty(){counter. reset(); expected(); } replace void push(Object a){counter. inc(); expected(a); } replace void pop() {counter. dec(); expected(); } } }

Data. With. Lock Component pairwise interaction Data/Lock component Data. With. Lock { participant Data

Data. With. Lock Component pairwise interaction Data/Lock component Data. With. Lock { participant Data { Lock lock; expect void init. Lock(); expect Any. Type method_to_wrap(Object[] args); replace Any. Type method_to_wrap(Object[] args) { if (lock. is_unlocked()) { lock(); expected(Object[] args); lock. unlock(); }}} private participant Lock {boolean l = true; void lock(){…}; void unlock(){…}; boolean is_unlocked(){return l}; }

Stack. Impl Queue. Impl Data. With. Counter Data. With. Lock

Stack. Impl Queue. Impl Data. With. Counter Data. With. Lock

First connector add. Counter&Lock { Stack. Impl is Data. With. Counter. Data. Structure with

First connector add. Counter&Lock { Stack. Impl is Data. With. Counter. Data. Structure with { void init. Counter() {counter = new Counter(); } void push(Object obj) {push(obj)); } // use name map instead Object top() {return top(); }. . . } is Data. With. Lock. Data with { method_to_wrap = {pop, push, top, make_empty, init. Counter}; }; Queue. Impl is Data. With. Counter. Data. Structure with {. . . } is Data. With. Lock. Data with {. . . }; }

Data. With. Counter Data. With. Lock Data. With. Counter&Lock

Data. With. Counter Data. With. Lock Data. With. Counter&Lock

Create composed aspects prior to deployment component Data. With. Counter. And. Lock { participant

Create composed aspects prior to deployment component Data. With. Counter. And. Lock { participant Data = Data. With. Counter. Data. Structure is Data. With. Lock. Data with { method-to-wrap = {make_empty, pop, top, push}}; }

Second connector: Deploy composed component connector add. Counter&Lock { Stack. Impl is Data. With.

Second connector: Deploy composed component connector add. Counter&Lock { Stack. Impl is Data. With. Counter. And. Lock. Data with { void make_empty() {empty(); } void init. Counter() { counter = new Counter(); } void push(Object obj) {push(obj); }. . . }; Queue. Impl is Data. With. Counter. And. Lock. Data with {. . . }; }

Defining New Behavior: The Publisher. Subscriber Aspect an aspect can be multiply deployed with

Defining New Behavior: The Publisher. Subscriber Aspect an aspect can be multiply deployed with the same application, each deployment with its own mappings.

Publisher component Publisher. Subscriber. Protocol { participant Publisher { expect void change. Op(Object[] args);

Publisher component Publisher. Subscriber. Protocol { participant Publisher { expect void change. Op(Object[] args); protected Vector subscribers = new Vector(); public void attach(Subscriber subsc) { subscribers. add. Element(subsc); } public void detach(Subscriber subsc) { subscribers. remove. Element(subsc); } replace void change. Op() { expected(); for (int i = 0; i < subscribers. size(); i++) {((Subscriber)subscribers. element. At(i)). new. Update(this); }}

Subscriber participant Subscriber { expect void sub. Update(Publisher publ); protected Publisher publ; public void

Subscriber participant Subscriber { expect void sub. Update(Publisher publ); protected Publisher publ; public void new. Update(Publisher a. Publ) { publ = a. Publ; sub. Update(publ); } }

Class for deployment class Change. Printer { void public print. R() { System. out.

Class for deployment class Change. Printer { void public print. R() { System. out. println("Printer: " + this. to. String() + " read access has occurred. . . " + n); } void public print. W() { System. out. println("Printer: " + this. to. String() + " write access has occurred. . . " + n); } void public notify. Change() { System. out. println("CHANGE. . . "); } }

Deployment 1 connector Pub. Sub. Conn 1 { Point is Publisher with { change.

Deployment 1 connector Pub. Sub. Conn 1 { Point is Publisher with { change. Op = {set*, get*}; } Change. Printer is Subscriber with { void sub. Update(Publisher publ) { notify. Change(); System. out. println(”on Point object " + ((Point) publ). to. String()); } } }

Deployment 2 connector Pub. Sub. Conn 2 { Tic. Tac. Toe is Publisher with

Deployment 2 connector Pub. Sub. Conn 2 { Tic. Tac. Toe is Publisher with { change. Op = {start. Game, new. Player, put. Mark, end. Game}}; {Board. Display, Status. Display} is Subscriber with { void sub. Update(Publisher publ) { set. Game((Game) publ); repaint(); } }; }

Deployment/write connector Pub. Sub. Conn 3 { Point is Publisher with { change. Op

Deployment/write connector Pub. Sub. Conn 3 { Point is Publisher with { change. Op = set*; } Change. Printer is Subscriber with { void sub. Update(Publisher publ) { print. W(); System. out. println("on point object " + ((Point) publ). to. String()); } } }

Deployment/read connector Pub. Sub. Conn 4 { Point is Publisher with { change. Op

Deployment/read connector Pub. Sub. Conn 4 { Point is Publisher with { change. Op = get*; } Change. Printer is Subscriber with { void sub. Update(Publisher publ) { print. R(); System. out. println("on point object " + ((Point) publ). to. String()); } } }

Overlap between connectors The sets of operations of Point that are mapped to different

Overlap between connectors The sets of operations of Point that are mapped to different notification operations of the subscriber participant need not be disjoint. For instance, we may want to distinguish between set operations that affect the x-coordinate, respectively, the ycoordinate of a point. The set(int, int), however, will then fall in both categories. This is expressed by the connectors Pub. Sub. Conn 3_1 and Pub. Sub. Conn 3_2 below.

Deployment/write connector Pub. Sub. Conn 3_1 { Point is Publisher with { change. Op

Deployment/write connector Pub. Sub. Conn 3_1 { Point is Publisher with { change. Op = {set, set. X}; } Change. Printer is Subscriber with { void sub. Update(Publisher publ) { print. W(); System. out. println("on point object " + ((Point) publ). to. String()); } } }

Deployment/write connector Pub. Sub. Conn 3_2 { Point is Publisher with { change. Op

Deployment/write connector Pub. Sub. Conn 3_2 { Point is Publisher with { change. Op = {set, set. Y}; } Change. Printer is Subscriber with { void sub. Update(Publisher publ) { print. W(); System. out. println("on point object " + ((Point) publ). to. String()); } } }

Mapping Participant Graphs • Is the deployment of a component giving the intended result?

Mapping Participant Graphs • Is the deployment of a component giving the intended result? • Example: Three participants: A, B, C – – A has 0. . * B; B has 1. . * C. A: : f(int x 1){for each b: f(x 1); } B: : f(int x 1){for each c: f(x); } // x a data member local to B C: : f(int x 1){print(“at C: number at previous B”); print(x 1); }

Expected output at C: number at previous B 78 at C: number at previous

Expected output at C: number at previous B 78 at C: number at previous B 8

Mapping A 0. . * A B 1. . * C C B

Mapping A 0. . * A B 1. . * C C B

Refinement This property must hold between a PG and a corresponding CG or another

Refinement This property must hold between a PG and a corresponding CG or another PG. The intent of the refinement relation is to ensure that the behavior in the component will be properly instantiated at the place of use without ``surprising'' behavior.

G 1 refinement G 2 F D E B C B E C G

G 1 refinement G 2 F D E B C B E C G 2 A refinement: connectivity of G 2 is in pure form in G 1 Allows extra connectivity. A G 1

G 1 refinement G 2 F D E B C B E C G

G 1 refinement G 2 F D E B C B E C G 2 A refinement: connectivity of G 2 is in pure form in G 1 A G 1

G 1 compatible G 2 F D E B C B E C G

G 1 compatible G 2 F D E B C B E C G 2 A Compatible: connectivity of G 2 is in G 1 A G 1

G 1 strong refinement G 2 F D E B C B E C

G 1 strong refinement G 2 F D E B C B E C G 2 A refinement: connectivity of G 2 is in pure form in G 1 and G 1 contains no new connections in terms of nodes of G 2 A G 1

Key concepts: refinement • Let G 1=(V 1, E 1) and G 2=(V 2,

Key concepts: refinement • Let G 1=(V 1, E 1) and G 2=(V 2, E 2) be directed graphs with V 2 a subset of V 1. Graph G 1 is a refinement of G 2 if for all u, v in V 2 we have that (u, v) in E 2 implies that there exists a path in G 1 between u and v which does not use in its interior a node in V 2. • Polynomial time.

Refinement • For each edge in G 2 there must be a corresponding pure

Refinement • For each edge in G 2 there must be a corresponding pure path in G 1. • Pure path = in interior no nodes of G 2. • Refinement = strong refinement with “if and only if” replaced by “implies”.

G 1 refinement G 2 F D E B C B E C G

G 1 refinement G 2 F D E B C B E C G 2 A Implementation: create strategy constraint map: bypassing all nodes A G 1

Refinement means: no surprises not G 1 refinement G 2 B C C B

Refinement means: no surprises not G 1 refinement G 2 B C C B G 2 A A G 1

Refinement means: no surprises G 1 refinement G 2 B C G 2 A

Refinement means: no surprises G 1 refinement G 2 B C G 2 A C B X A G 1

Refinement means: no surprises not G 1 refinement G 2 B C C B

Refinement means: no surprises not G 1 refinement G 2 B C C B G 2 A G 1 A

Alternative definition a graph G is a refinement of a graph S, if S

Alternative definition a graph G is a refinement of a graph S, if S is a connected subgraph of the pure transitive closure of G with respect to the node set of S.

Pure transitive closure • The pure transitive closure of G=(V, E) with respect to

Pure transitive closure • The pure transitive closure of G=(V, E) with respect to a subset W of V is the graph G*=(V, E*), where E*={(i, j): there is a Wpure path from vertex i to vertex j in G}. • A W-pure path from i to j is a path where i and j are in W and none of the inner nodes of the path are in W.

Implementation issues • Translate to Aspect. J: requires source code access. • What if

Implementation issues • Translate to Aspect. J: requires source code access. • What if aspectual components only in binary? • Want separate compilation of application and aspectual components.

Interfaces between components and application • Usage interface – expected in order to be

Interfaces between components and application • Usage interface – expected in order to be used by participants • Modification interface – expected in order to be replaced by the aspect

Writing components directly in Java • Benefit: no new language to learn

Writing components directly in Java • Benefit: no new language to learn

Participants as abstract classes class Auto. Reset { abstract class Data. To. Reset {

Participants as abstract classes class Auto. Reset { abstract class Data. To. Reset { abstract void reset(); protected int count = 0; void replaced_set. Op(Object this. Object, Class this. Class, Method expected_set. Op, Object[] args) { if ( ++count >= 100 ) { expected_set. Op. invoke(this. Object, args); count = 0; reset(); } }

Auto. Reset component Auto. Reset { participant Data. To. Reset { expect void set.

Auto. Reset component Auto. Reset { participant Data. To. Reset { expect void set. Op(Object[] args); expect void reset(); protected int count = 0; replace void set. Op(Object[] args) { if ( ++count >= 100 ) { expected(args); count = 0; reset(); }} } }

What about connectors in Java? • Translation to Java not straight-forward

What about connectors in Java? • Translation to Java not straight-forward

Approach • Methods in – usage interface: abstract methods – modification interface: x() translated

Approach • Methods in – usage interface: abstract methods – modification interface: x() translated to replaced_x(). Expected implementation of x() will be a parameter to replaced_x(). x=set. Op void replaced_set. Op(Object this. Object, Class this. Class, Method expected_set. Op, Object[] args) { if ( ++count >= 100 ) { expected_set. Op. invoke(this. Object, args);

Approach Question: Why is this. Class used as an argument? Is not used in

Approach Question: Why is this. Class used as an argument? Is not used in this example. void replaced_set. Op(Object this. Object, Class this. Class, Method expected_set. Op, Object[] args) { if ( ++count >= 100 ) { expected_set. Op. invoke(this. Object, args);

Binary Adaptation of Application Classes • Application classes are turned into event publishers by

Binary Adaptation of Application Classes • Application classes are turned into event publishers by adding a field that stores a set of subscribers (from connectors). • Any application operation op that is mapped to expected operation in modification interface: renamed to expected_op • new implementation of op: invokes notify on subscribers

Pseudo-code simulating binary adaptation class Point { public static java. util. Vector aspect. Subscribers;

Pseudo-code simulating binary adaptation class Point { public static java. util. Vector aspect. Subscribers; // added variable public void add. Subscriber(Aspect. Subscriber sub) { // added operation aspect. Subscribers. add. Element((Object) sub); } private int x = 0; private int y = 0;

Pseudo-code simulating binary adaptation void expected_set(int x, int y) { // renamed this. x

Pseudo-code simulating binary adaptation void expected_set(int x, int y) { // renamed this. x = x; this. y = y; } void set(int x, int y) { // reimplemented Object[] args = {(Object) new Integer(x), (Object) new Integer(y)}; Class[] arg. Types = {Integer. TYPE, Integer. TYPE}; Method expected_set = this. Class. get. Method("expected_set", arg. Types); Enumeration subscribers = aspect. Subscribers. elements(); while (subscribers. has. Elements()) { Object sub = subscribers. next(); sub. notify(this, this. get. Class(), expected_set, args); }} …}

Generating Connector Classes • Generate a class for each connector • Example: Composition. Conn

Generating Connector Classes • Generate a class for each connector • Example: Composition. Conn 3

Recall: Composition. Conn 1 connector Composition. Conn 1 { {Line, Point} is Show. Read.

Recall: Composition. Conn 1 connector Composition. Conn 1 { {Line, Point} is Show. Read. Write. Access. Data. To. Access with { read. Op = get*; write. Op = set*; }; Point is Auto. Reset. Data. To. Reset with { set. Op = set*; void reset() { x = 0; y = 0; } }; {Line, Point, Rectangle} is Instance. Logging. Data. To. Log; }

Recall: Composition. Conn 3 connector Composition. Conn 3 extends Composition. Conn 1 { Point

Recall: Composition. Conn 3 connector Composition. Conn 3 extends Composition. Conn 1 { Point is Auto. Reset. Data. To. Reset with { { set. Op = set; void reset() { x = 0; y = 0; }} { set. Op = set. X; void reset() { x = 0; }} { set. Op = set. Y; void reset() { y = 0; }} }; }

Connector Class code interface Aspect. Subscriber { void notify(Object this. Object, Class this. Class,

Connector Class code interface Aspect. Subscriber { void notify(Object this. Object, Class this. Class, Method expected_meth, Object[] args); } interface Evaluable { void eval(Object this. Object, Class this. Class, Method expected_meth, Object[] args); }

Connector class generated class Composition. Conn 3 implements Aspect. Subscriber { public static single.

Connector class generated class Composition. Conn 3 implements Aspect. Subscriber { public static single. Instance = new Composition. Conn 3(); java. util. Hashtable mappings = new java. util. Hashtable(); abstract class Point_Data. To. Reset_set. Op extends Auto. Reset. Data. To. Reset implements Evaluable { private Point host; public void eval(Object this. Object, Class this. Class, Method expected_meth, Object[] args) { host = (Point) this. Object; replaced_set. Op(this. Object, this. Class, expected_meth, args); } }

public Composition. Conn 3() { Object[] arg. Types = {Integer. TYPE, Integer. TYPE}; Method

public Composition. Conn 3() { Object[] arg. Types = {Integer. TYPE, Integer. TYPE}; Method expected_set = Point. get. Method("expected_set", arg. Types); arg. Types = {Integer. TYPE}; Method expected_set. X = Point. get. Method("expected_set. X", arg. Types); Method expected_set. Y = Point. get. Method("expected_set. Y", arg. Types); mappings. put((Object) expected_set, (Object) new Point_Data. To. Reset_set. Op() { void reset() {host. expected_set(0, 0); }}); mappings. put((Object) expected_set. X, (Object) new Point_Data. To. Reset_set. Op() { void reset() {host. expected_set. X(0); }}); mappings. put((Object) expected_set. Y, (Object) new Point_Data. To. Reset_set. Op() { void reset() {host. expected_set. Y(0); }}); Point. add. Subscriber(this); }

Component. Package Component Participant expected. Op(){expected. Op(); get. Host()} get. Host() Connector. Package My.

Component. Package Component Participant expected. Op(){expected. Op(); get. Host()} get. Host() Connector. Package My. Host to. Modify(){part. Op(); }// override aux(){super. to. Modify()} part Participant. Anonymous. Extended expected. Op(){aux()} get. Host(){My. Host. this} main(){new My. Host(). to. Modify() } “outer. super. to. Modify()” simulated with aux() is not elegant Johan’s solution 1 based on Mira’s inner class solution. Modification interface Host. Package is represented also by Host // to play role of participant abstract class, not by to. Modify(){} Method argument as // to be mapped to Op() proposed by Mira.

Component. Package Component Participant expected. Op(){expected. Op(); get. Host()} get. Host() Connector. Package My.

Component. Package Component Participant expected. Op(){expected. Op(); get. Host()} get. Host() Connector. Package My. Host to. Modify(){part. Op(); } aux(){super. to. Modify()} part Participant. Anonymous. Extended expected. Op(){aux()} get. Host(){My. Host. this} main(){new My. Host(). to. Modify() } Problem: when we have a Host-object and want to get Does it work with multiple modified behavior, need to create a My. Host object. participants? With multiple hosts? Host. Package Looks like. Host to. Modify(){}

Component. Package Component Participant expected. Op(){expected. Op(); get. Host()} get. Host() More complex connector:

Component. Package Component Participant expected. Op(){expected. Op(); get. Host()} get. Host() More complex connector: one host but multiple methods are modified. Creates multiple inheritance!? Is this why Mira’s implementation is more complex? Connector. Package My. Host to. Modify 1(){part 1. Op(); }//overrride aux 1(){super. to. Modify 1()} part 1 Participant. Anonymous. Extended expected. Op(){aux 1()} get. Host(){My. Host. this} main(){new My. Host(). to. Modify 1()} part 2 Participant. Anonymous. Extended expected. Op(){aux 2()} get. Host(){My. Host. this} main(){new My. Host(). to. Modify 2()} to. Modify 2(){part 2. Op(); }//override aux 2(){super. to. Modify 2()}