<< §1.1 Team classes | ↑ Table of Contents ↑ | §1.3 Acquisition and implicit inheritance of role classes >> |
§1.2 Role classes and objects
Each direct inner class of a team is a role class.
Just like inner classes, each instance of a role class has an implicit reference
to its enclosing team instance. This reference is immutable.
Within the implementation of a role it can be accessed by qualifying the identifier
this
with the name of the team class, as in:
1 | public team class MyTeamA { |
2 | public class MyRole { |
3 | public void print() { System.out.println("Team: "+ MyTeamA.this); } |
4 | } |
5 | } |
Creation of role instances is further restricted as defined in §2.4. Teams can also define role interfaces just like role classes. With respect to role specific properties a role interface is treated like a fully abstract class.
§1.2.1 Modifiers for roles
↑ §1.2
Member classes of a team cannot be static
.
Also the use of access modifiers for roles is restricted and modifiers have different (stronger) semantics than for
regular classes (see below). With respect to accessibility a team acts mainly like a package regarding its roles.
(a) Role class protection
A role class must have exactly one of the access modifiers public
or protected
.
This rule does not affect the class modifiers abstract
, final
and strictfp
.
(b) protected role classes
A protected
role can only be accessed from within the enclosing
team or any of its sub-teams. The actual border of encapsulation is the
enclosing team instance. The rules for protected roles are given
in §1.2.3 below.
(c) public role classes
Only public
roles can ever be accessed outside their enclosing team.
Accessing a role outside the enclosing team instance is governed by the rules
of externalized roles, to be defined next (§1.2.2).
(d) abstract role classes
A role class has to be marked abstract if any of its methods
is not effective.
The methods of a role class comprise direct methods and
methods acquired by inheritance.
In addition to regular inheritance a role class may acquire methods
also via implicit inheritance (§1.3.1).
A method may become effective by either:
- implementation (i.e., a regular method body), or
- a callout binding (see §3).
§2.5 discusses under which circumstances abstract roles force the enclosing team to be abstract, too.
(e) Role features
Access modifiers for members of roles have some special interpretation:
- A private member is also visible in any implicit sub role
(see implicit inheritance §1.3.1.(c)).
In contrast to inner classes in Java, private members of a role are not visible to the enclosing team. - The default visibility of role members restricts access to the current class and its sub-classes (explicit and implicit).
protected
role members can only be accessed from the enclosing team or via callin (§4).public
role members grant unrestricted access.
Additionally, a role always has access to all the features that its enclosing team has access to.
Only public
members can ever be accessed via an externalized role (§1.2.2).
(f) Static role methods
In contrast to inner classes in pure Java, a role class may indeed define static methods. A static role method requires no role instance but it still requires a team instance in scope. Static role methods can be called:
- from the enclosing team,
- via callin (see §4.7).
Within a static role method the syntax MyTeam.this
is available for accessing the enclosing team instance.
(g) No static initializers
A static field of a role class must not have a non-constant initialization expression. Static initialization blocks are already prohibited for inner classes by Java (see JLS §8.1.2).
Note:
Static initialization generally provides a means for performing initialization code prior to instantiation, i.e., at class-loading time. Before any role can be created already two levels of initialization are performed: (1) The (outer most) enclosing team class performs static initializations when it is loaded. (2) Any enclosing team executes its constructor when it is instantiated. It should be possible to allocate any early initialization to either of these two phases instead of using static role initializers.§1.2.2 Externalized roles
↑ §1.2
Normally, a team encapsulates its role against unwanted access from the outside. If roles are visible outside their enclosing team instance we speak of externalized roles.
Externalized roles are subject to specific typing rules in order to ensure, that role instances from different team instances cannot be mixed in inconsistent ways. In the presence of implicit inheritance (§1.3.1) inconsistencies could otherwise occur, which lead to typing errors that could only be detected at run-time. Externalized roles use the theory of "virtual classes" [1], or more specifically "family polymorphism" [2], in order to achieve the desired type safety. These theories use special forms of dependent types. Externalized roles have types that depend on a team instance.
§1.2.3 deduces even stronger forms of encapsulation from the rules about externalized roles.
(b) Declaration with anchored type
Outside a team role types are legal only if denoted relative to an existing team instance (further on called "anchored types"). The syntax is:
final MyTeam myTeam = expression; RoleClass<@myTeam> role = expression;
The syntax Type<@anchor>
is a special case of a parameterized type, more specifically a value dependent type (§9).
The type argument (i.e., the expression after the at-sign) can be a simple name or a path. It must refer to an instance
of a team class.
The role type is said to be anchored to this team instance.
The type-part of this syntax (in front of the angle brackets) must be the simple name of a role type directly contained
in the given team (including roles that are acquired by implicit inheritance).
Note:
Previous versions of the OTJLD used a different syntax for anchored types, where the role type was prefixed with the anchor expression, separated by a dot (anchor.Type
,
see §A.6.3). A compiler may still support that path syntax but it should be flagged as being deprecated.
(c) Immutable anchor
Anchoring the type of an externalized role to a team instance
requires the team to be referenced by a variable which
is marked final
(i.e., immutable).
The type anchor can be a path v.f1.f2...
where
v
is any final variable and f1
...
are final fields.
(d) Implicit type anchors
The current team instance can be used as a default anchor for role types:
- In non-static team level methods role types are by default interpreted as anchored to
this
(referring to the team instance). I.e., the following two declarations express the same:public RoleX getRoleX (RoleY r) { stmts } public RoleX<@this> getRoleX (RoleY<@this> r) { stmts }
- In analogy, role methods use the enclosing team instance as the default anchor for any role types.
Note, that this
and Outer.this
are always
final
.
The compiler uses the pseudo identifier tthis
to denote
such implicit type anchors in error messages.
(e) Conformance
Conformance between
two types RoleX<@teamA>
and RoleY<@teamB>
not only requires the role types to be compatible, but also
the team instances to be provably the same object.
The compiler must be able to statically analyze anchor identity.
(f) Substitutions for type anchors
Only two substitutions are considered for determining team identity:
-
For type checking the application of team methods,
this
is substituted by the actual call target. For role methods a reference of the formOuter.this
is substituted by the enclosing instance of the call target. - Assignments from a
final
identifier to anotherfinal
identifier are transitively followed, i.e., ift1, t2
are final, after an assignmentt1=t2
the typesR<@t1>
andR<@t2>
are considered identical. OtherwiseR<@t1>
andR<@t2>
are incommensurable.
Attaching an actual parameter to a formal parameter in a method call is also considered as an assignment with respect to this rule.
(g) Legal contexts
Anchored types for externalized roles may be used in the following contexts:
- Declaration of an attribute
- Declaration of a local variable
- Declaration of a parameter or result type of a method or constructor
- In the
playedBy
clause of a role class (see §2.1).
It is not legal to inherit from an anchored type, since this would require membership of the referenced team instance, which can only be achieved by class nesting.
Note:
Item 4. — within the given restriction — admits the case where the same class is a role of one team and the base class for the role of another team. Another form of nesting is defined in §1.5.(h) Externalized creation
A role can be created as externalized using either of these equivalent forms:
outer.new Role() new Role<@outer>()
This requires the enclosing instance outer
to be
declared final
. The expression has the
type Role<@outer>
following the rules of
externalized roles.
The type Role
in this expression must be a simple
(unqualified) name.
(i) No import
It is neither useful nor legal to import a role type.
Rationale:
Importing a type allows to use the unqualified name in situations that would otherwise require to use the fully qualified name, i.e., the type prefixed with its containing package and enclosing class. Roles, however are contained in a team instance. Outside their team, role types can only be accessed using an anchored type which uses a team instance to qualify the role type. Relative to this team anchor, roles are always denoted using their simple name, which makes importing roles useless.A static import for a constant declared in a role is, however, legal.
Example code (Externalized Roles):
1 | team class FlightBonus extends Bonus { |
2 | public class Subscriber { |
3 | void clearCredits() { ... } |
4 | } |
5 | void unsubscribe(Subscriber subscr) { ... } |
6 | } |
7 | class ClearAction extends Action { |
8 | final FlightBonus context; |
9 | Subscriber<@context> subscriber; |
10 | ClearAction (final FlightBonus bonus, Subscriber<@bonus> subscr) { |
11 | context = bonus; // unique assignment to 'context' |
12 | subscriber = subscr; |
13 | } |
14 | void actionPerformed () { |
15 | subscriber.clearCredits(); |
16 | } |
17 | protected void finalize () { |
18 | context.unsubscribe(subscriber); |
19 | } |
20 | } |
§1.2.3 Protected roles
↑ §1.2
Roles can only be public
or protected
.
A protected
role is encapsulated
by its enclosing team instance. This is enforced by these rules:
(a) Importing role classes
This rule is superseded by §1.2.2.(i)
(b) Qualified role types
The name of a protected
role class may never be used qualified, neither
prefixed by its enclosing type nor parameterized by a variable as type anchor (cf. §1.2.2.(a)).
(c) Mixing qualified and unqualified types
An externalized role type is never compatible to an unqualified role type,
except for the substitutions in §1.2.2.(f), where
an explicit anchor can be matched with the implicit anchor this
.
Rules (a) and (b) ensure that the name of a protected role class cannot be used outside the lexical scope of its enclosing team. Rule (c) ensures that team methods containing unqualified role types in their signature cannot be invoked on a team other than the current team. Accordingly, for role methods the team context must be the enclosing team instance.
(d) Levels of encapsulation
Since protected role types can not be used for externalization, instances of these types are already quite effectively encapsulated by their enclosing team. Based on this concept, encapsulation for protected roles can be made even stricter by the rules of role confinement. On the contrary, even protected roles can be externalized as opaque roles which still expose (almost) no information. Confinement and opaque roles are subject of §7.
§1.2.4 Type tests and casts
↑ §1.2
In accordance with §1.2.2.(e), in OT/J
the instanceof
operator and type casts have extended semantics for roles.
(a) instanceof
For role types the instanceof
operator yields true only if
both components of the type match: the dynamic role type must be compatible
to the given static type, and also type anchors must be the same instance.
(b) Casting
Casts may also fail if the casted expression is anchored to a different
team instance than the cast type. Such failure is signaled by a
org.objectteams.RoleCastException
.
(c) Class literal
A class literal of form R.class
is dynamically bound to the class R
visible in the current instance context. Using a class literal for a role outside its
enclosing team instance (see §1.2.2) requires the following syntax:
RoleClass<@teamAnchor>.class
§1.2.5 File structure
↑ §1.2
Just like regular inner classes, role classes may be inlined in the source code of the enclosing team. As an alternative style it is possible to store role classes in separate role files according to the following rules:
(a) Role directory
In the directory of the team class a new directory is created which has the same name as the team without the .java suffix.
(b) Role files
Role classes are stored in this directory (a). The file names are
derived from the role class name extended by .java.
A role file must contain exactly one top-level type.
(c) package statement
A role class in a role file declares as its package the fully qualified
name of the enclosing team class. The package statement of a role file
must use the team
modifier as its first token.
(d) Reference to role file
A team should mention in its javadoc comment each role class which is stored externally using a @role tag.
(f) Imports in role files
A role file may have imports of its own.
Within the role definition these imports are visible in addition to all imports of the enclosing team.
Only base
imports (see §2.1.2.(d))
must be defined in the team.
Semantically, there is no difference between inlined role classes and those stored in separate role files.
Note:
Current Java compilers disallow a type to have the same fully qualified name as a package. However, the JLS does not seem to make a statement in this respect. In OT/J, a package and a type are interpreted as being the same team, if both have the same fully qualified name and both have theteam
modifier.
Role file example:
in file org/objectteams/examples/MyTeamA.java :
|
|
1 | package org.objectteams.examples; |
2 | /** |
3 | * @author Stephan Herrmann |
4 | * @date 20.02.2007 |
5 | * @file MyTeamA.java |
6 | * @role MyRole |
7 | */ |
8 | public team class MyTeamA { |
9 | ... |
10 | } |
in file org/objectteams/examples/MyTeamA/MyRole.java :
|
|
1 | team package org.objectteams.examples.MyTeamA; |
2 | public class MyRole { |
3 | ... |
4 | } |
<< §1.1 Team classes | ↑ Table of Contents ↑ | §1.3 Acquisition and implicit inheritance of role classes >> |
Effects:
Action
which is used to associate the action of resetting a subscriber's credits to a button or similar element in an application's GUI.context
(line 8) and parameterbonus
(line 10) serve as anchor for the type of externalized roles.subscriber
(line 9) and parametersubscr
(line 10) store a Subscriber role outside the FlightBonus team.final
and prior to the role assignment a team assignment has taken place (line 11).Note, that the Java rules for definite assignments to final variables ensure that exactly one assignment to a variable occurs prior to its use as type anchor. No further checks are needed.
clearCredits
(line 15). This method call is also an example for implicit team activation (§5.3.(b)).unsubscribe
is for this call expanded tocontext
forthis
). This proves identical types for actual and formal parameters.