§2.1. playedBy relation
(a)
Role-base binding
Roles are bound to a base class by the
playedBy keyword.
| 1 |
public team class MyTeamA { |
| 2 |
public class MyRole playedBy MyBase { |
| 3 |
... |
| 4 |
} |
| 5 |
} |
|
(b)
Inheritance
The
playedBy relation is inherited along
explicit and implicit (
§1.3.1(c))
role inheritance.
(c)
Covariant refinement
An
explicit sub-role (sub-class using
extends)
can refine the
playedBy relation to a more
specific base class (this is the basis for
smart lifting (§2.3.3)).
If a role class inherits several
playedBy relations from
its super-class and its super-interfaces, there must be a most specific
base-class among these relations, which is conform to all other base-classes.
This most specific base-class is the base-class of the current role.
(d)
No-variance
An
implicit sub-role (according to
§1.3.1(c))
may only add a
playedBy relation but never change an existing one.
Note however, that implicit inheritance may implicitly specialize an existing
playedBy relation. If the base class is specified relative to some implicit (
OuterTeam.this) or explicit (
OuterTeam.base) team anchor, refining that team anchor automatically refines the playedBy declaration, too.
This rule never requires any action from a programmer but only explains the interpretation of a playedBy declaration in
complex situations.
(e)
Use of playedBy bindings
The
playedBy relation by itself has no effect
on the behavior of role and base objects.
It is, however, the precondition for translation polymorphism
(lowering:
§2.2 and lifting:
§2.3)
and for method bindings
(callout:
§3 and callin:
§4).
(f)
Effect on garbage collection
A role and its base object form one conceptual entity. The garbage collector will see a role
and its base object as linked in a bidirectional manner. As a result, a role cannot be
garbage collected if its base is still reachable and vice versa.
Internally a team manages its roles and corresponding bases using weak references.
When using one of the
getAllRoles(..)
methods (see
§6.1(a)),
the result may be non-deterministic because these internal structures
may hold weak references to objects that will be collected by the next run of the
garbage collector. We advise clients of
getAllRoles(..) to call
System.gc() prior to calling
getAllRoles(..) in order
to ensure deterministic results.
§2.1.1. Binding interfaces
Role base bindings may involve classes and/or interfaces.
An interface defined as a member of a team is a role interface and may therefore
have a
playedBy clause. Also the type mentioned after the
playedBy keyword may be an interface.
Implementation limitation:
The language implementation as of OTDT version 0.9.X cannot yet bind
a role class to a base interface, but this restriction will go in the future.
§2.1.2. Legal base classes
Generally, the base class mentioned after
playedBy must be
visible in the enclosing scope (see
below for an exception).
Normally, this scope is defined just by the enclosing team.
For role files (
§ 1.2.5(b))
also additional imports in the role file are considered.
§ 2.1.2(d) below defines how imports can be constrained so that certain types can be used as base types, only.
(a)
No role of the same team
The base class of any role class must not be a role of the same team.
It is also not allowed to declare a role class of the same name
as a base class bound to this or another role of the enclosing team,
if that base class is given with its simple name and resolved using a regular import.
Put differently, a base class mentioned after
playedBy
may not be
shadowed by any role class of the enclosing team.
Base imports as defined below (
§2.1.2(e)) relax this rule by allowing to import a class as a base class only. In that case no shadowing occurs since the scopes for
base classes and roles are disjoint.
(b)
No cycles
The base class mentioned after playedBy may not be
an enclosing type (at any depth) of the role class being defined.
This rule prohibits the creation of cycles where the base instance of
a given role R contains roles of the same type R.
More generally any sequence of classes C1, C2, .. Cn
were each Ci+1 is either a member or the base class of Ci and Cn = C1 is forbidden.
Conversely, it is also prohibited to bind a role class to its own inner class.
(c)
Base class decapsulation
If a base class referenced after
playedBy exists but is not visible under normal visibility rules of Java, this restriction may be overridden. This concept is called
decapsulation, i.e., the opposite of encapsulation (see also
§ 3.4). Base class decapsulation can be configured as either
ignore,
warn or
error.
Decapsulation is not allowed if the base class is a confined role (see
§ 7.2).
Within the current role a decapsulated base class can be mentioned in the right-hand-side of any method binding (
callout or
callin). Also arguments in these positions are allowed to mention the decapsulated base class:
(d)
Base imports
If the main type in a file denotes a team, the modifier
base can be applied to an import in order to specify that this type should be imported for application as a base type only. Example:
| 1 |
import base some.pack.MyBase; |
| 2 |
public team class MyTeam { |
| 3 |
protected class MyRole playedBy MyBase { } // simple name resolves to imported class |
| 4 |
MyBase illegalDeclaration; // base import does not apply for this position |
| 5 |
} |
|
Types imported by a base import can only be used in the same positions where also base class decapsulation (
§ 2.1.2(c)) is applicable.
It is recommended that a type mentioned after the keyword
playedBy is always imported with the
base modifier, otherwise the compiler will give a warning.
Base imports create a scope that is disjoint from the normal scope. Thus, names that are imported as base will never clash
with normally visible names (in contrast to
§ 1.4). More specifically, it is not a problem to use a base class's name also for its role if a base import is used.
§2.2. Lowering
Each instance of a bound role class internally stores a reference to its
base object. The reference is guaranteed to exist for each bound role
instance, and cannot be changed during its lifetime.
(a)
Definition of lowering
Retrieving the base object from a role object is called lowering.
No other means exists for accessing the base reference.
(b)
Places of lowering
The lowering translation is not meant to be invoked
by client code, but
implicit translations are inserted by
the compiler at all places where a role type is provided while the
corresponding base type (or a super type) was expected.
In other words: lowering translations are inserted by the compiler at
all places in a program which would otherwise not be type correct
and which using lowering are statically type correct.
This may concern:
- the right hand side of an assignment wrt. the
static type of the left hand side,
- the argument values of a method or constructor call
wrt. the static type of the corresponding formal parameter,
- the return value of a method compared to the declared
return type of the method.
- a role parameter in a callout binding (§ 3.3(d))
- or the return value in a callin binding (§ 4.5(d))
| 1 |
public team class MyTeamA { |
| 2 |
public class MyRole playedBy MyBase { ... } |
| 3 |
void useMyBase(MyBase myb) {...} |
| 4 |
MyRole returnMyRole() {...} |
| 5 |
public void doSomething() { |
| 6 |
MyRole r = new MyRole(); |
| 7 |
MyBase b = r; |
| 8 |
useMyBase(r); |
| 9 |
MyBase b2 = returnMyRole(); |
| 10 |
} |
| 11 |
} |
|
Lowering translations are not inserted for
- reference comparison (using
== or !=)
instanceof checks
- cast expressions
- return values in callout bindings § 3.3(d))
- parameters in callin bindings (§ 4.5(d))
For cases where lowering shall be
forced see
§2.2(d) below.
(c)
Typing
The static type of an implicit lowering translation is the base class
declared using playedBy in the respective role class.
(d)
Explicit lowering
If a base type is also the super type of its role,
which frequently happens, if a base reference is known only by
the type
Object, lowering cannot be deduced automatically,
since a type could be interpreted both as a role type and a base type.
These cases may need
explicit lowering.
For this purpose the role class must declare to implement the interface
ILowerable (from
org.objectteams.Team).
This will cause the compiler to generate a method
public Object lower()
for the given role class. Client code may use this method to
explicitly request the base object of a given role object.
| 1 |
public team class MyTeamA { |
| 2 |
public class MyRole implements ILowerable playedBy MyBase { ... } |
| 3 |
public void doSomething() { |
| 4 |
MyRole r = new MyRole(); |
| 5 |
Object oMyRole = r; |
| 6 |
Object oMyBase = r.lower(); |
| 7 |
} |
| 8 |
} |
|
(e)
Lowering of arrays
Lowering also works for arrays of role objects.
In order to lower an array of role objects,
a new array is created and filled with base objects, one for each
role object in the original array. The array may have any number
of dimensions at any shape. The lowered array will have exactly the
same shape.
Note, that each lowering translation will create a new array.
§2.3. Lifting
Lifting is the reverse translation of lowering. However, lifting is
a bit more demanding, since a given base object may have zero to
many role objects bound to it. Therefor, the lifting translation
requires more context information and may require to create role
objects on demand.
(a)
Definition of lifting
Retrieving a role for a given base object is called
lifting.
Lifting is guaranteed to yield the same role object for subsequent
calls regarding the same base object, the same team instance and
the same role class (see
§2.3.4
for cases of ambiguity that are signaled by compiler warnings
and possibly runtime exceptions).
(b)
Places of lifting
The lifting translation is not meant to be invoked
by client code, but translations are inserted by the compiler
at the following locations:
(c)
Typing
A lifting translation statically expects a specific role class.
This expected role class must have a playedBy clause
(either directly, or inherited (explicitly or implicitly)
from a super role), to which the given base type is conform.
(d)
Lifting of arrays
Lifting also works for arrays of role objects.
For lifting an array of base objects
a new array is created and filled with role objects, one for each
base object in the original array. In contrast to the role objects
themselves, lifted arrays are never reused for subsequent lifting
invocations.
The term translation polymorphism
describes the fact
that at certain points values can be passed which are not
conform to the respective declared type considering only regular
inheritance (
extends). With translation polymorphism
it suffices that a value can be translated using lifting or lowering.
§2.3.1. Implicit role creation
Lifting tries to reuse existing role objects so that role state persists across
lifting and lowering. If no suitable role instance is found during lifting,
a new role is created.
(a)
Reuse of existing role objects
A role object is considered suitable for reuse during lifting, if
these three items are identical:
- the given base object
- the given team object
- the statically required role type
For the relation between the statically required role type and
the actual type of the role object see
§2.3.3
("smart lifting").
(b)
Default lifting constructor
Lifting uses a default constructor which takes exactly one argument of the type
of the declared base class (after
playedBy).
By default the compiler generates such a constructor for each bound role.
On the other hand, default constructors that take no arguments
(as in
JLS 8.8.7) are never generated for bound roles.
The super-constructor to be invoked by a default lifting constructor
depends on whether the role's super class is a bound role or not.
- If the super-class is a bound role, the default lifting constructor will invoke the default lifting constructor of the super-class.
- If the super-class is not a bound role, the default lifting constructor will invoke the normal argumentless default constructor
of the super-class.
(c)
Custom lifting cunstructor
If a role class declares a custom constructor with the same signature
as the default lifting constructor, this constructor is used during lifting.
This custom constructor may pre-assume that the role has been setup
properly regarding its base-link and registered in the team's internal map of roles.
If a bound role has an unbound super-class without an argumentless
constructor, providing a custom lifting constructor is obligatory,
because no legal default lifting constructor can be generated.
§2.3.2. Declared Lifting
(a)
Parameters with declared lifting
A non-static team-level method or constructor may declare a parameter with two types
in order to explicitly denote a place of
lifting.
Using the syntax
public void m (BaseClass as RoleClass param) { stmts }
a liftable parameter can be declared, provided the second type
(
RoleClass) is a role of (
playedBy) the first type
(
BaseClass).
Furthermore, the role type must be a role
of the enclosing team class defining the given method. The role type
must be given by its simple (ie., unqualified) name.
Such a signature requires the caller to provide a base object
(here
BaseClass), but
the callee receives a role object (here
RoleClass).
In fact, the client sees a signature in which the "
as RoleClass"
part is omitted.
Compatibility between caller and callee sides is achieved by
an implicitly inserted lifting translation. A signature using declared lifting
is only valid, if the requested lifting is possible
(see
§2.3.3 and
§2.3.4 for details).
(b)
Super in the context of declared lifting
Calling
super or
tsuper in a method or constructor which
declares lifting for one or more parameters refers to a method
or constructor with role type parameters,
i.e., lifting takes place
before super invocation.
(c)
Declared lifting of arrays
If a parameter involving explicit lifting should be of an
array type,
the syntax is
public void m (BaseClass as RoleClass param[]) ...
Here the brackets denoting the array apply to both types,
BaseClass
and
RoleClass.
(d)
Declared lifting for catch blocks
Also the argument of a catch block may apply declared lifting like in:
catch (BaseException as RoleClass param) { stmts }
This syntax is only valid in a non-static scope of a team (directly or nested).
In the given example,
RoleClass must be played by
BaseException. Note, that
RoleClass itself need not be a throwable. As the effect of this declaration the catch block will catch any exception of type
BaseException and provides it wrapped with a
RoleClass instance to the subsequent block.
Also note, that re-throwing the given instance
param has the semantics of implicitly lowering the role to its base exception before throwing, because the role conforms to the
required type
Throwable only via lowering.
Example code:
| 1 |
team class Super { |
| 2 |
void m (MyRole o) { ... }; |
| 3 |
} |
| 4 |
team class Sub extends Super { |
| 5 |
void m (MyBase as MyRole o) { |
| 6 |
// inside this method o is of type MyRole |
| 7 |
super(o); |
| 8 |
} |
| 9 |
} |
| 10 |
Sub s_team = new Sub(); |
| 11 |
MyBase b = new MyBase(); |
| 12 |
s_team.m(b); // clients see a parameter "MyBase o" |
|
§2.3.3. Smart Lifting
In situations where role and base classes are part of some inheritance
hierarchies (extends), choosing the appropriate role class during
lifting involves the following rules:
(a)
Static adjustment
If a base class
B shall be lifted to a role class
R that is not bound to (
playedBy)
B, but if a subclass of
R
— say
R2 —
is bound to
B, lifting is statically setup to use
R2, the most general subclass of
R that
is bound to
B or one of its super-types.
Restriction: This step is not applicable for
replace
callin bindings (
§4.5.d).
(b)
Dynamic selection of a role class
At runtime also the dynamic type of a base object is considered:
Lifting always tries to use a role class that is bound to the
exact class of the base object. Lifting considers all role–base
pairs bound by playedBy such that the role class is a
sub-class of the required (statically declared) role type
and the base class is a super-class of the
dynamic type of the base object.
From those possible pairs the most specific base class is chosen.
If multiple role classes are bound to this base class the most
specific of these classes is chosen.
(c)
Team as closed world
In the above analysis gathering all role-base pairs is performed at
compile-time. From this follows, that a team class can only be
compiled when all its contained role classes are known and a role class
can never be compiled without its team.
The analysis includes all roles and their bindings that are inherited
from the super-team.
(d)
Selection regardless of abstractness
Smart lifting is not affected by abstractness of role classes.
For the effect of abstract role classes see
§2.5.
Complex Example:

| role class |
base class |
| class R1 |
|
| class R2 extends R1 playedBy B2 |
class B2 |
| class R3 extends R2 /* inherited: playedBy B2 */ |
class B3 extends B2 |
| class R4 extends R3 playedBy B4 |
class B4 extends B3 |
| class R5 extends R4 /* inherited: playedBy B4 */ |
|
| |
class B6 extends B4 |
| class R7 extends R5 playedBy B7 |
class B7 extends B6 |
If the inheritance hierarchies of the involved base and role classes are given (like in the figure above) the smart lifting
algorithm can be rephrased to the following "graphical" rule:
|
Starting with the dynamic base type (B6 in the example) move upwards the the inheritance relation until you reach a base class bound to a role class indicated by
a «playedBy» arrow pointing to the base class (B4). Switch to the role side along this arrow (R4). Now move downwards the role inheritance hierarchy as long as the subrole does not refine the playedBy relationship (indicated
by another «playedBy» arrow). The bottom role you reach this way (R5) is the role type selected by smart lifting.
|
§2.3.4. Binding ambiguities
While all examples so far have only shown 1-to-1 class bindings,
several cases of multiple bindings are allowable. Ambiguities may be
detected at compile time and/or at runtime.
(a)
Potential ambiguity
A
potential ambiguity is given,
if two role classes
R1 and
R2
exist such that
R1 and R2 are played by the
same base class B, and
R1 and R2 have a common
super role R0,
which is also bound to a base class, and
- neither role class
R1 nor
R2 is a (indirect) sub-class of the other.
| Effect: |
|
|
In this case the compiler issues a warning, stating that the
B may not be liftable, because both
role classes R1 and R2 are candidates
and there is no reason to prefer one over the other. If no potential ambiguity is detected, lifting will always
be unambiguous. |
In the above situation, trying to lift an instance of type
B to the role type
R0 is an
illegal lifting request. If
R0 is bound to the same base class
B as its sub-roles
R1 and
R2 are, role
R0 is
unliftable, meaning that no instance of
R0 can ever by obtained by lifting.
Example code:
| 1 |
team class MyTeam { |
| 2 |
public class SuperRole playedBy MyBase {...} |
| 3 |
public class SubRoleA extends SuperRole {...} |
| 4 |
public class SubRoleB extends SuperRole {...} |
| 5 |
} |
|
(b)
Definite ambiguity
A
definite ambiguity is given if
- the situation of potential ambiguity according to (a)
above is given and
- lifting is requested
(either by method binding or explicitly
(§2.3.2))
from the shared base class
B to any role
class R0 that is a common super role for
R1 and R2.
| Effect: |
|
|
Definite ambiguity is a compile time error.
|
Example code:
| 1 |
team class MyTeam { |
| 2 |
public class SuperRole playedBy MyBase {...} |
| 3 |
public class SubRoleA extends SuperRole playedBy SubBase {...} |
| 4 |
public class SubRoleB extends SuperRole playedBy SubBase {...} |
| 5 |
|
| 6 |
public void useSuperRole(SubBase as SuperRole r) {...} |
| 7 |
} |
|
(c)
Actual ambiguity
At runtime
actual ambiguity may occur if for the
dynamic type of a base to be lifted the conditions of (b)
above hold accordingly. Actual ambiguity is only possible in cases
reported by the compiler as potential ambiguity.
| Effect: |
|
|
An actual ambiguity is reported at runtime by a
LiftingFailedException.
|
Example code:
| 1 |
team class MyTeam { |
| 2 |
public class SuperRole playedBy MyBase {...} |
| 3 |
public class SubRoleA extends SuperRole playedBy SubBase {...} |
| 4 |
public class SubRoleB extends SuperRole playedBy SubBase {...} |
| 5 |
|
| 6 |
public void useSuperRole(MyBase as SuperRole r) {...} |
| 7 |
} |
| 8 |
// plus these calls: |
| 9 |
MyTeam mt = new MyTeam(); |
| 10 |
mt.useSuperRole(new SubBase()); |
|
(d)
Mismatching role
In cases of potential ambiguity another runtime error may occur:
a
mismatching role is encountered when a role is found
in the cache, which is not conform to the required type.
This happens, if the base object has previously been lifted
to a type that is incompatible with the currently requested type.
| Effect: |
|
|
This is reported by a WrongRoleException.
|
Example code:
| 1 |
team class MyTeam { |
| 2 |
public class SuperRole playedBy MyBase {...} |
| 3 |
public class SubRoleA extends SuperRole {...} |
| 4 |
public class SubRoleB extends SuperRole {...} |
| 5 |
|
| 6 |
public void useRoleA(MyBase as SubRoleA r) {...} |
| 7 |
public void useRoleB(MyBase as SubRoleB r) {...} |
| 8 |
} |
| 9 |
// plus these calls: |
| 10 |
MyTeam mt = new MyTeam(); |
| 11 |
MyBase b = new MyBase(); |
| 12 |
mt.useRoleA(b); // creates a SubRoleA for b |
| 13 |
mt.useRoleB(b); // finds the SubRoleA which is not compatible to the expected type SubRoleB. |
|
From the second item of (a) follows, that for binding ambiguities different
role hierarchies are analyzed in isolation.
For this analysis only those role classes are considered that are bound to a
base class (directly using playedBy or by inheriting this relation
from another role class).
I.e., two role classes that have no common bound super role will never cause
any ambiguity.
§2.4. Explicit role creation
Lifting is the normal technique by which role objects are created implicitly.
This section defines under which conditions a role can also be created explicitly.
§2.4.1. Role creation via a lifting constructor
Lifting uses the default constructor for roles (see
§2.3.1).
This constructor can be invoked from client code, if the following rules are respected.
(a)
Team context
The lifting constructor can be used only within the enclosing team of
the role to be instantiated. Thus, qualified allocation expressions
(someTeam.new SomeRole(..))
may never use the lifting constructor.
(b)
Fresh base object
If the argument to a lifting constructor invocation is a new
expression, creating a fresh base object, the use of the lifting constructor
is safe. Otherwise the rules of (c) below apply.
(c)
Duplicate role runtime check
If it cannot be syntactically derived, that the argument to a lifting
constructor is a freshly created base object (b), a compile time warning will
signal that an additional runtime check is needed: It must be prevented that
a new role is created for a base object, which already has a role of the
required type in the given team. It is not possible to replace an existing
role by use of the lifting constructor. At runtime, any attempt to do so
will cause a
org.objectteams.DuplicateRoleException to be thrown.
This exception can only occur in situations where the mentioned compile
time warning had been issued.
§6.1 will introduce reflective functions
which can be used to manually prevent errors like a duplicate role.
§2.4.2. Role creation via a regular constructor
Roles may also be created explicitly using a custom constructor with arbitrary signature
other than the signature of the lifting constructor.
Within role constructors, four kinds of self-calls are possible:
|
base(..) |
|
|
A constructor of the corresponding base class
(§A.5.3(c)).
|
|
this(..) |
|
|
Another constructor of the same class
|
|
super(..) |
|
|
A constructor of the super-class (normal extends),
unless the super-class is bound to a different base class,
in which case calling super(..) is not legal.
|
|
tsuper(..) |
|
|
A constructor of the corresponding role of the super-team
(§A.5.4(e)). Also see the constraint
in §1.3.2(c).
|
(a)
Unbound roles
Each constructor of a role that is not bound to a base class must use
one of this(..), super(..) or tsuper(..).
(b)
Bound roles
Each constructor of a
bound role must directly or indirectly invoke either a
base(..) constructor or a lifting constructor (see
§2.3.1). Indirect calls to the base constructor or lifting constructor may use any of
this(..),
super(..) or
tsuper(..), which simply delegates the obligation to the called constructor.
If a constructor referenced by
base(..) is not visible according to the
regular rules of Java, it may still be called using
decapsulation (see
also
§3.4,
§2.1.2(c)).
Note, that if the super or tsuper role is not bound, delegating the obligation to that unbound role will not work.
(c)
Super-call for bound roles
Instead of or prior to calling
base(..) a constructor of a bound role explicitly or implicitly calls a super constructor.
Which constructor is applicable depends on the super role and its
playedBy clause.
- If the super role is bound to the same base class as the current role is,
- not writing a super-call causes the lifting constructor of the super role to be invoked.
- explicitly calling a super constructor requires the super constructor to either
- create a role instance using a base constructor call (directly or indirectly), or
- be a lifting constructor receiving a base instance, which the current role must provide as the argument.
- If the super role is bound but the current role refines the
playedBy relationship (cf. §2.1(c)),
- a lifting constructor must be called explicity passing a base object as the argument.
- If the role has an explicit or implicit super role which is unbound the constructor may optionally call a super constructor
(using
super(..) or tsuper(..)) prior to calling base(..). Otherwise the default constructor is implicitly invoked.
When invoking a lifting constructor of a super role the base object can optionally be obtained by using a base constructor
call as an expression:
super(base(<args>));
The language system evaluates the base constructor by creating an
instance of the appropriate base class using a constructor with matching
signature. Also the internal links are setup that are needed for accessing the
base object from the role and for lifting the base object to the new role
in the future.
The syntax for base constructors follows the rule that role implementations
never directly refer to any names of base classes or their features.
§2.4.3. Role creation in the presence of smart-lifting
Explicitly instantiating a role
R1 bound to a base
B where smart lifting of
B to
R1 would actually provide a subrole
R2 is dangerous: Instantiation enters the
R1 into the team's internal cache. If at any time later lifting this
B to
R2 is requested, which is a legal request, the runtime system will answer by throwing a
WrongRoleException because it finds the
R1 instead of the required
R2.
For this reason, in this specific situation the explicit instantiation
new R1(..) will be flagged by a warning. The problem can be avoided by using
R2 in the instantiation expression.
| 1 |
public class B { void bm() {} } |
| 2 |
public team class T { |
| 3 |
protected class R1 playedBy B {...} |
| 4 |
protected class R2 extends R1 { // inherits the binding to B. |
| 5 |
void rm() { /* body omitted */ } |
| 6 |
rm <- after bm; |
| 7 |
} |
| 8 |
public B getDecoratedB() { |
| 9 |
return new R1(new B()); // compile-time warning! |
| 10 |
} |
| 11 |
} |
| 12 |
// plus these calls: |
| 13 |
T t = new T(); |
| 14 |
t.activate(); |
| 15 |
B b = t.getDecorateB(); // creates an R1 for b |
| 16 |
b.bm(); // => WrongRoleException! |
|
§2.5. Abstract Roles
Overriding of role classes and dynamic binding of role types
(
§1.3.1(e))
adds new cases to
creation with respect to abstract classes.
(a)
Using abstract classes for creation
Abstract role classes can indeed be used for object creation.
The effect of such a statement is that the team must be
marked abstract. Only those sub-teams are concrete
that provide concrete versions for all role classes used in
creation expressions.
This includes the case, where a
super-team has a concrete role class and creates
instances of this role class and only the sub-team changes
the status of this role class to abstract. Also here
the sub-team must be marked abstract, because it contains
an abstract role class that is used in creation expressions.
(b)
Relevant roles
A team must be marked
abstract if one of its
relevant roles is abstract.
A role is relevant in this sense if
- the role class is public or if
- an explicit
new expression
would require to create instances of the role class, or if
- any of the lifting methods of the enclosing team
would require to create instances of the role class.
A role is irrelevant with respect to lifting
if either of the following holds:
- It is not bound to a base class, neither directly nor
by an inherited
playedBy clause.
- It has a sub-role without a
playedBy clause.
- It is bound to an abstract base class, and for all concrete
sub-classes of the base class, a binding to a
more specific role class exists.
If neither property, relevance nor irrelevance, can be shown for an abstract role,
a warning is given in case the enclosing team is not abstract.
§2.6. Explicit base references
The role-base link is not meant to be accessed explicitly from programs,
but it is fully under the control of compiler and runtime environment.
Accessing features of a role's base object is done by
callout bindings (§ 3).
Yet, a keyword
base exists, which can be used in the following
contexts:
(a)
Externalized roles of a base team
If the base class of a role
T1.R1 is again a team
T2, roles of that team
T2 can be
externalized (see
§ 1.2.2)
using
base as their type anchor. Given that
R2 is a role of
T2, one could write:
| 1 |
public team class T1 { |
| 2 |
protected class R1 playedBy T2 { |
| 3 |
protected R2<@base> aRoleOfMyBase; |
| 4 |
} |
| 5 |
} |
|
This syntax is only legal within the body of the role
T1.R1 which is bound to the team
T2 containing role
R2.
A static type prefix can be used to disambiguate a base anchor, so the explicit variant of the above type would be
R2<@R1.base>.
It is not legal to use a type anchor containing
base as an element in a path of references like
<@base.field> or
<@field.base>.
(b)
Explicit base object creation
Within a role constructor (which is not the lifting constructor)
the syntax
base(arguments) causes an instance
of the bound base class to be created and linked
(see
§ 2.4.2).
(c)
Base call in callin method(d)
Base guard predicatesGuard predicates (§ 5.4) can
be specified to act on the base side using the
base when keywords. Within such a base guard
predicate
base is interpreted as a special identifier
holding a reference to the base object that is about to be lifted
for the sake of a callin method interception
(see
§ 5.4.2(a)).
(e)
Parameter mappings
An expression at the right-hand side of a parameter mapping (parameter in a callin binding (
§ 4.4) or result in a callout binding (
§ 3.2(a)) ) may use the keyword
base to refer to the bound base instance. Such usage requires the role method bound in this method binding to be non-static.
(f)
Inhibition of modification
In all cases, the base reference is immutable,
i.e., base can never appear as the left-hand-side of an
assignment.