§2.1.2 Legal base classes

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.