↑ Table of Contents ↑ | §2.2 Lowering >> |
§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 (this advanced situation is illustrated in §2.7.(d)).
(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
↑ §2.1
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 2.0 imposes one particular restriction when binding a role to a base interface: A role binding to a base interface may not contain any callin bindings (§4).§2.1.2 Legal base classes
↑ §2.1
Generally, the base class mentioned after playedBy
must be
visible in the enclosing scope (see below (§2.1.2.(c)) for an exception).
Normally, this scope is defined just by the imports of 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.(d)) 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) Cycles
The base class mentioned after playedBy
should normally not be
an enclosing type (at any depth) of the role class being defined.
This rule discourages the creation of cycles where the base instance of
a given role R
contains roles of the same type R
.
More generally this concerns any sequence of classes C1, C2, .. Cn
were each Ci+1
is either a member or the base class of
Ci
and Cn = C1
.
Such structures may be difficult to understand and have certain restrictions regarding
callout (§3.1.(a)) and base constructor calls (§2.4.2).
It is furthermore recommended to equip all roles that are played by an enclosing class with a guard predicate (§5.4) like this:
base when (MyTeam.this == base)
This will avoid that the role adapts other instances of the enclosing class which are not the enclosing instance.
It is 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). A compiler should signal any occurrence of base class decapsulation. If a compiler supports to
configure warnings this may be used to let the user choose to (a) ignore base class decapsulation, (b) treat it as a warning
or even
(c) treat it as an error.
Binding to a final
base class is also considered as decapsulation, since a playedBy
relationship has
powers similar to an extends
relationship, which is prohibited by marking a class as final
.
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 (§3) or callin (§4)). Also arguments in these positions are allowed to mention the decapsulated base class:
- the first argument of one of the role's constructors (see lifting constructor (§2.4.1)).
- the base side of an argument with declared lifting (see declared lifting (§2.3.2)).
(d) Base imports
If the main type in a file denotes a team, the modifier base
can be applied to a single 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 | // simple name resolves to imported class: |
4 | protected class MyRole playedBy MyBase { } |
5 | MyBase illegalDeclaration; // base import does not apply for this position |
6 | } |
Base imports cannot be on-demand imports (using the wildcard .*
).
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.
(e) No free type parameters
Neither the role class nor the base class in a playedBy binding must have any free type parameters. If both classes are specified with a type parameter of the same name, both parameters are identified and are not considered as free.
From this follows that a role class cannot have more type parameters than its base. Conversely, only one situation exists where a base class can have more type parameters than a role class bound to it: if the role class has no type parameters a generic base class can be bound using the base class's raw type, i.e., without specifying type arguments.
Note:
The information from theplayedBy
declaration is used at run-time
to associate role instances to base instances.
Specifying a base class with free type parameters would imply that only such base instances
are decorated by a role whose type is conform to the specified parameterized class.
However, type arguments are not available at run-time, thus the run-time environment
is not able to decide which base instances should have a role and which should not.
This is due to the design of generics in Java which are realized by erasure.
The following example shows how generics can be used in various positions. Note, that some of the concepts used in the example will be explained in later sections.
1 | public class ValueTrafo<T> { |
2 | public T transform(T val) throws Exception { /* ... */ } |
3 | } |
4 | public team class TransformTeam { |
5 | protected class SafeTrafo<U> playedBy ValueTrafo<U> { |
6 | U transform(U v) -> U transform(U val); |
7 | protected U safeTransform(U v) { |
8 | try { |
9 | return transform(v); |
10 | } catch (Exception e) { |
11 | return v; |
12 | } |
13 | } |
14 | } |
15 | <V> V perform(ValueTrafo<V> as SafeTrafo<V> trafo, V value) { |
16 | return trafo.safeTransform(value); |
17 | } |
18 | } |
19 | ... |
20 | ValueTrafo<String> trafo = new ValueTrafo<String>(); |
21 | TransformTeam safeTrafo = new TransformTeam(); |
22 | String s = safeTrafo.perform(trafo, "Testing"); |
23 |
↑ Table of Contents ↑ | §2.2 Lowering >> |
Explanation
U
where the type parameter is identified with the corresponding type parameter of the role's base class (which is originally declared asT
in line 1.U
around.String
.