Latest News

§2 Role Binding

Roles and base classes

playedBy relation
A role can be bound to a class outside the team by a playedBy relation, which declares that each role instances is associated to a base instances.
Base class
The class to which a role is bound (using playedBy) is called its base class. Role instances may inherit and override features from their base instance, which is declared using callout (§3) and callin (§4) method bindings.
Bound role
Each role class that declares a playedBy relation is called a bound role. The term bound role may also be used for the instances of such a class.
Lifting / lowering
Translations between a role and its base are called lifting (base to role) (§2.3) and lowering (role to base) (§2.2).
Translation polymorphism
Conformance between a role and a base is governed by translation polymorphism, which refers to a substitutability that is achieved using either lifting or lowering.
Declared lifting
Generally, lifting happens implicitly at data flows between a role object and its base. Team level methods provide additional data flows, where lifting may be declared explicitly.

§2.1 playedBy relation↑ §2

(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:

(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
  // simple name resolves to imported class:
4
  protected class MyRole playedBy MyBase { } 
5
  MyBase illegalDeclaration; // base import does not apply for this position
6
}

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 the playedBy 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
Explanation
  • Line 5 shows a role with type parameter U where the type parameter is identified with the corresponding type parameter of the role's base class (which is originally declared as T in line 1.
  • Line 6 shows a callout binding (§3) which mappes a base method to a corresponding role method while maintaining the flexible typing.
  • The regular method in lines 7-13 just passes values of type U around.
  • The generic method in line 15 ff. uses declared lifting (§2.3.2) to obtain a role for a given base object. The method has no knowledge about the concrete type arguments of either role nor base, but works under the guarantee that both type arguments will be the same for any single invocation.
  • Lines 20 ff. finally create instances of base and team and invoke the behavior thereby instantiating type parameters to String.

§2.2 Lowering↑ §2

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(new MyBase());
7
    MyBase b = r;
8
    useMyBase(r);
9
    MyBase b2 = returnMyRole();
10
  }
11
}
Effects:

An instance of type MyRole is lowered to type MyBase when

  • assigning it to b (line 7)
  • passing it as argument to a method with formal parameter of type MyBase (line 8)
  • assigning the return value to a variable of type MyBase (line 9)

Note: The constructor call in line 6 uses the lifting constructor as defined in §2.4.1

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.ITeam). 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(new MyBase());
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.

(f) Ambiguous lowering

When assigning a value of a bound role type to a variable or argument of type java.lang.Object this situation is considered as ambiguous lowering because the assignment could apply either (a) a direct upcast to Object or (b) lowering and then upcasting. In such situations the compiler will not insert a lowering translation, but a configurable warning will be issued.

§2.3 Lifting↑ §2

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↑ §2.3

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:

  1. the given base object
  2. the given team object
  3. the statically required role type

For the relation between the statically required role type and the actual type of the role object see "smart lifting" (§2.3.3).

(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 constructor

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.

(d) Fine-tuning role instantiation

If the lifting operation as defined above degrades the program performance, the lifting semantics can be modified per role class by adding the annotation @org.objectteams.Instantiation which requires an argument of type org.objectteams.InstantiationPolicy in order to select between the following behaviors:

ONDEMAND
This is the default behavior as defined above.
ALWAYS
This strategy avoids maintaining the internal role cache, but instead a fresh role instance is created for each lifting request. This may increase the number of role instances but cuts the costs of accessing the cache, which could otherwise become expensive if a cache grows large. As a result of this strategy role state can no longer be shared over time, thus it is discouraged to define fields in a role with this strategy. Also, comparing roles could lead to unexpected results. Therefor, roles with this strategy should implement custom equals and hashCode methods, which should simply delegate to the base instance (using callout §3).
NEVER
Roles with this instantiation policy are never instantiated by lifting. Such roles cannot define non-static fields. Otherwise this optimization is fully transparent, specifically callout bindings will refer to the correct base instance.
As of version 2.0 the OT/J compiler does not implement this strategy.
SINGLETON
Roles declaring this strategy will be instantiated at most once per team. Subsequent lifting requests in the same team will always answer the same role instance. Such roles may receive triggers from callin bindings, but cannot define callout bindings.
As of version 2.0 the OT/J compiler does not implement this strategy.

§2.3.2 Declared lifting↑ §2.3

(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 (i.e., 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. Nevertheless, the super method may also have a declared lifting signature. It will then see the same role instance(s) as the current method.

(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.

(e) Generic declared lifting

A method with declared lifting may introduce a type parameter that is bounded relative to a given role type. Such bound is declared as:

<AnyBase base SuperRole>
void teamMethod(AnyBase as SuperRole arg) {
   // body using arg as of type SuperRole
}

This means that AnyBase is a type parameter whose instantiations must all be liftable to role SuperRole.

The given type bound requires the call site to supply an argument that is compatible to any base class for which the current team contains a bound role that is a sub class of SuperRole, including SuperRole itself. However, SuperRole itself need not be bound to any base class. On the other hand, different valid substitutions for AnyBase need not be related by inheritance.

Note:
This feature supports generalized treatment of otherwise unrelated base classes. This is done by defining one bound role for each base under consideration and by having all these roles extend a common unbound role.
Example code (Declared Lifting):
1
team class Super {
2
  public class MyRole playedBy MyBase { ... }
3
  void m (MyRole o) { ... };
4
}
5
team class Sub extends Super {
6
  void m (MyBase as MyRole o) {
7
    // inside this method o is of type MyRole
8
    super.m(o);
9
  }
10
}
11
Sub s_team = new Sub();
12
MyBase b = new MyBase();
13
s_team.m(b); // clients see a parameter "MyBase o"
Effects:
  • Clients use method m with a base instance (type MyBase) as its argument (line 13).
  • Before executing the body of m, the argument is lifted such that the method body receives the argument as of type MyRole (line 8).

§2.3.3 Smart lifting↑ §2.3

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 parameter mappings of 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:

smart lifting 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 declarations require lifting B3 to R1 this is statically refined to use R2 instead, because this is the most general class declaring a binding to a super–class of B3.
  • If the dynamic base type in the same situation is B6, three steps select the appropriate role:
    1. By searching all playedBy clauses (including those that are inherited) the following role–base pairs are candidates:
      (R2,B2), (R3,B2), (R4,B4) and (R5,B4).
    2. From these pairs the two containing the most specific base class B4 are chosen.
    3. This makes R4 and R5 role candidates, from which the most specific R5 is finally chosen.

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). This role class must be conform to the requested role type. 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↑ §2.3

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 B0, and
  • neither role class R1 nor R2 is a (indirect) sub-class of the other.
Note:
According to §2.1.(c), if B is distinct from B0 it has to be a sub-class of B0.
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 (Potential Ambiguity):
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.

Definite binding ambiguity also occurs in cases of generic declared lifting §2.3.2.(e) if the specified role R is unbound and if two independent sub-roles R1 and R2 exist that introduce a playedBy binding to the same base class BX. In this case no potential ambiguity is flagged because roles R1 and R2 have no shared bound super-role.

Effect:
Code causing definite ambiguity is required to handle org.objectteams.LiftingFailedException.

In cases of definite binding ambiguity lifting will indeed fail except for some corner cases. Such corner cases may arise if lifting already finds an appropriate role in the cache or if an (indirect) subrole of the ambiguously bound role is an unambiguous lift target for the concrete type of the base object at run-time. See also §2.3.5.

Example code (Definite Ambiguity):
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) {...} // must declare LiftingFailedException
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 or definite ambiguity.

Effect:
An actual ambiguity is reported at runtime by throwing a org.objectteams.LiftingFailedException.
Example code (Actual Ambiguity):
1
import org.objectteams.LiftingFailedException;
2
team class MyTeam {
3
  public class SuperRole playedBy MyBase {...}
4
  public class SubRoleA extends SuperRole playedBy SubBase {...}
5
  public class SubRoleB extends SuperRole playedBy SubBase {...}
6
  
7
  public void useSuperRole(MyBase as SuperRole r) throws LiftingFailedException {...}
8
}
9
// plus these calls:
10
MyTeam mt = new MyTeam();
11
mt.useSuperRole(new SubBase()); // will throw a LiftingFailedException

(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 throwing a org.objectteams.WrongRoleException.
Example code (Mismatching Role):
1
import org.objectteams.LiftingFailedException;
2
					team class MyTeam {
3
  public class SuperRole playedBy MyBase {...}
4
  public class SubRoleA extends SuperRole {...}
5
  public class SubRoleB extends SuperRole {...}
6
  
7
  public void useRoleA(MyBase as SubRoleA r) throws LiftingFailedException {...}
8
  public void useRoleB(MyBase as SubRoleB r) throws LiftingFailedException {...}
9
}
10
// plus these calls:
11
MyTeam mt = new MyTeam();
12
MyBase b = new MyBase();
13
mt.useRoleA(b); // creates a SubRoleA for b
14
mt.useRoleB(b); // finds the SubRoleA which is not compatible
15
                // to the expected type SubRoleB.

From the second item of §2.3.4.(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.3.5 Consequences of lifting problems↑ §2.3

The rules for lifting and role binding allow (after issuing a warning) two problematic situations:

  1. A potential binding ambiguity makes selection of the approprate role type impossible (§2.3.4.(a))
  2. A role which might be relevant for lifting is abstract (§2.5.(b))

Whenever lifting fails for one of these reasons an org.objectteams.LiftingFailedException (§6.2.(d)) is thrown. Given that this is a checked exception and depending on the location requiring lifting this has the following consequences:

(a) Problematic declared lifting

A method with declared lifting (§2.3.2) may have to declare org.objectteams.LiftingFailedException.

(b) Problematic callout binding

The role method of a callout binding with result lifting (§3.3.(c)) may have to declare org.objectteams.LiftingFailedException.

(c) Problematic callin binding

A callin binding (§4) may silently fail due to a org.objectteams.LiftingFailedException. This exception will actually remain hidden because the callin binding is not explicitly invoked from any source code but implicitly by the runtime dispatch mechanism. To signal this situation the compiler raises an error against such callin binding.

However, the compiler should allow to configure this error and understand the warning token "hidden-lifting-problem" for suppressing this problem (§4.1.(b)). If the problem is ignored/suppressed and if at runtime the lifting problem occurs, triggering of the callin binding will silently fail, i.e., the program will continue in this situation as if the binding hadn't existed in the first place.

(d) Incompatible redefinition of a role hierarchy

Consider a team T1 with a method m with declared lifting regarding role R, where no lifting problems are detected. Consider next a sub-team T2 which modifies the hierarchy of role R such that lifting to T2.R is problematic due to a binding ambiguity. In this case clients invoking T1.m() could face the situation at runtime that an instance of T2 is used that unexpectedly fails to lift to its role R. Here, the compiler signals a specific error against T2 alerting of the incompatible change.

§2.4 Explicit role creation↑ §2

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↑ §2.4

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↑ §2.4

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)), unless the role is involved in base class circularity (§2.1.2.(b)), in which case a base constructor call is illegal.
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
      1. create a role instance using a base constructor call (directly or indirectly), or
      2. 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 explicitly 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↑ §2.4

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 org.objectteams.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.

Example code (WrongRoleException):
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
  }
7
  public B getDecoratedB() {
8
    return new R1(new B()); // compile-time warning!
9
  }
10
  public void requestLifting(B as R2 r) {}
11
}
12
// plus these calls:
13
T t = new T();
14
B b = t.getDecoratedB(); // creates an R1 for b
15
t.requestLifting(b); // => org.objectteams.WrongRoleException!
  • A note on line 8: this line passes a fresh instance of B to the lifting constructor of R1 (see §2.4.1.(b)). In order to return this B instance lowering is implicitly used for the return statement.
  • When line 15 is executed, a lifting of b to R2 is requested but due to line 8 an R1 is found in the internal cache.

§2.5 Abstract Roles↑ §2

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.

Interpretation:
Since the type in a role creation expression is late-bound relative to the enclosing team instance, abstract role classes can be seen as the hook in a template&hook pattern that is raised from the method level to the class level: A super-team may already refer to the constructor of an abstract role class, only the sub-team will provide the concrete role class to fill the hook with the necessary implementation.

(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↑ §2

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

Within a callin method (§4.2.(d)) an expression base.m(args) is used to invoke the originally called method (see §4.3).

(d) Base guard predicates

Guard 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.(c)) ) 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.

(g) Decapsulation via base reference

In cases §2.6.(d) and §2.6.(e) above, members of the base object may be accessed that would not be visible under Java's visibility rules. Such references are treated as decapsulation in accordance with §3.4.(a) and §3.5.(e).
Note that accessing a base field via base only gives reading access to this field.

§2.7 Advanced structures↑ §2

This section discusses how role containment and the playedBy relationship can be combined. It does not define new rules, but illustrates rules defined above. The central idea is that any class can have more than one of the three flavors team, role, and base.

(a) Nesting

If a role (contained in a team) is also a team (marked with the team modifier) it is a nested team. The depth of nesting is not restricted.

(b) Stacking

If the base class to which a role is bound using playedBy is a team, the role is said to be stacked on the base team.

(c) Layering

If roles of a team Secondary are played by roles of another team Primary (i.e., base classes are roles), the team Secondary defines a layer over the team Primary. Such layering requires a final reference anchor from Secondary to an instance of Primary. All playedBy declarations within Secondary specify their base classes anchored to that final link anchor.

Team layering example

Due to the anchored base types, layered teams implicitly support the following guarantee: all base objects of roles of Secondary are contained within the team instance specified by the link anchor. If roles of Secondary contain any callin bindings to non-static base methods, these will be triggered only when a base method is invoked on a base instance contained in the team specified by anchor.
In accordance with §2.6.(a) the anchor in such anchored playedBy declarations could also be the pseudo identifier base, provided that Secondary is a nested team, which has a playedBy binding to Primary as its base class. This situation is part of the second example below (§2.7.(d)) (see T1 playedBy TB1).

(d) Implicit playedBy specialization

According to §2.1.(d) an implicit sub-role may implicitly specialize an existing playedBy relation. This requires the base class to be specified relative to some implicit (OuterTeam.this) or explicit (OuterTeam.base) team anchor. Specializing that team anchor automatically specializes 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.

Two advanced examples demonstrating the above are:
  • If a role TOuter1.T.R of a nested team TOuter1.T is played by another role of the outer enclosing team TOuter1.B, subclassing the outer team TOuter1 to TOuter2 will produce a new role TOuter2.T.R which is automatically played by TOuter2.B, an implicit sub class of the original base class TOuter1.B.
Implicitly overriding playedBy
  • Consider the case where a nested T1 as a role of TOuter is stacked on a base team TB1. Also, T1 is a layered team over TB1 because its role R adapts role TB1.B.
    In this situation the playedBy relation of role TOuter.T1.R is given by a base-anchored type B<@T1.base>. If furthermore TOuter.T1 is subclassed to TOuter.T2 which covariantly refines the inherited playedBy declaration to TB2, then TOuter.T2.R will automatically refine the inherited playedBy relation to TB2.B to follow the new interpretation of the base anchor.
Implicitly overriding playedBy base