ObjectTeams/Java Language Definition - §1.

§1. Teams and Roles

Fundamental concepts of Teams

Teams and Roles Classes that are defined with the modifier team are called team classes, or teams for short.
Direct inner classes of a team are called role classes, or roles for short.
Role inheritance Inheritance between teams introduces a special inheritance relationship between their contained roles. The rules of this implicit inheritance are given below (§ 1.3.1).
Externalized role Roles are generally confined to the context of their enclosing team instance. Subject to specific restrictions, a role may be passed outside its team using the concept of externalized roles.

§1.1. Team classes

A class declared with the modifier team is a team class (or team for short).
1
 public team class MyTeamA {
2
   ...
3
 }

Teams are meant as containers for roles, which are defined in the following paragraphs.
1
 public team class MyTeamA {
2
   public class MyRole {
3
     ...
4
   }
5
 }

Teams introduce a new variant of inheritance for contained role classes (see §1.3.1 below). Other properties of teams, which are defined in later sections, are:
§5. Team activation
§2.5 abstractness and instantiation
§2.3.2 declared lifting in team methods
§6.1 reflective functions defined in org.objectteams.Team
Apart from these differences, team classes are regular Java classes with methods and fields, whose instances are regular Java objects.

§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

With respect to accessibility a team acts like a package regarding its roles.
(a) Role class protection
A role class must have exactly one of the access modifiers public or protected.
(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 will discuss 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:
  1. 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.
  2. The default visibility of role members restricts access to the current class and its sub-classes (explicit and implicit).
  3. protected role members can be accessed from the enclosing team only.
  4. public role members grant unrestricted access.
Only public members can ever be accessed via an externalized role.
(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.

Note, that already two levels of initialization are performed prior to role instantiation: static initialization of the (outer most) enclosing team class and instantiation of the enclosing team(s). Static initialization of a role class would add nothing new to this.

§1.2.2. Externalized roles

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", or more specifically "family polymorphism", 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 will deduce even stronger forms of encapsulation from the rules about externalized roles.

(a) Visibility
Only instances of a public role class can ever be externalized.
(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;
myTeam.RoleClass role = expression
In the syntax anchor.Type the expression to the left of the dot can be a simple name or a path. It must refer to an instance of a team class, the type at the right hand side of the dot must be the simple name of a role directly contained in the given team (including roles that are acquired by implicit inheritance).
Note: The syntax presented in this section is no longer recommended. Section §9.2.1 defines an alternative syntax for anchored types, which is more versatile. Generally, any anchored type myTeam.RoleClass can also be written as RoleClass<@myTeam>.
(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:
  1. 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 this.RoleX getRoleX (this.RoleY r) { stmts }
  2. 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 teamA.RoleX and teamB.RoleY 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:
  1. For type checking the application of team methods, this is substituted by the actual call target. For role methods a reference of the form Outer.this is substituted by the enclosing instance of the call target.

  2. Assignments from a final identifier to another final identifier are transitively followed, i.e., if t1, t2 are final, after an assignment t1=t2 the types t1.R and t2.R are considered identical. Otherwise t1.R and t2.R 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:
  1. Declaration of an attribute
  2. Declaration of a local variable
  3. Declaration of a parameter or result type of a method or constructor
  4. 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,that 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 will be defined in §1.5.
(h) Qualified creation
Creating a role as externalized using the syntax
outer.new Role()
requires the enclosing instance outer to be declared final. The expression has the type outer.Role 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. Note that outside their team role types can only be used relative to a type anchor, which implicitly grants access to all its public roles.

Example code ("Externalized Roles"):

1
 team class FlightBonus extends Bonus {
2
   public class Subscriber {
3
     void clearCredits() { ... };
4
   }
5
   void unshow(this.Subscriber subscr) { ... }
6
 }

7
 class ClearAction extends Action {
8
   final FlightBonus context;
9
   context.Subscriber subscriber;
10
   ClearAction (final FlightBonus bonus,
11
                bonus.Subscriber subscr) {
12
     context    = bonus;   // unique assignemt to 'context'
13
     subscriber = subscr;
14
   }
15
   void actionPerformed () {
16
     subscriber.clearCredits();
17
   }
18
   void finalize () {
19
     context.unshow(subscriber);
20
   }
21
 }

Effects:

  • Lines 1-6 show a terse extract of a published example [NODe02].
  • Lines 7-21 show a sub-class of 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.
  • Attribute context (line 8) and parameter bonus (line 10) serve as anchor for the type of externalized roles.
  • Attribute subscriber (line 9) and parameter subscr (line 11) store a Subscriber role outside the FlightBonus team.
  • In order to type-check the assignment in line 13, the compiler has to ensure that the types of LHS and RHS are anchored to the same team instance. This can be verified by checking that both anchors are indeed final and prior to the role assignment a team assignment has taken place (line 12).
    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.
  • It is now legal to store this role reference and use it at some later point in time, e.g., for invoking method clearCredits (line 16). This method call is an example for implicit team activation (§5.3(b)).
  • Line 19 demonstrates how an externalized role can be passed to a team level method. The signature of unshow is for this call expanded to
    void unshow(context.Subscriber subscr)
    (by substituting the call target context for this). This proves identical types for actual and formal parameters.

§1.2.3. Protected roles

Since roles can only be public or protected, any role that is not public is a protected role which is encapsulated by its enclosing team instance. This is enforced by these rules:
(a) Importing role classes
It is not allowed to import a protected role class.
(b) Qualified role types
The name of a protected role class may not be used qualified, neither by its enclosing type nor by a variable as type anchor. I.e., it may never appear right of a dot (".") (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

In accordance with §1.2.2(e), in ObjectTeams/Java 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.

§1.2.5. File structure

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.
(e) Legal types in role files
The type in a role file must not be an enum.
Semantically, there is no difference between inlined role classes and those stored in separate role files.
Note, that 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 the team modifier.

in file org/objectteams/examples/MyTeamA.java :
1
 package org.objectteams.examples;
2
 public team class MyTeamA {
3
   /**
4
    * @author Stephan Herrmann
5
    * @date 20.02.2002
6
    * @file MyTeamA.java
7
    * @role MyRole
8
    */
9
   ... 
10
 }

in file org/objectteams/examples/MyTeamA/MyRole.java :
11
 team package org.objectteams.examples.MyTeamA;
12
 public class MyRole { 
13
   ...
14
 }

§1.3. Team and role inheritance

The predefined class org.objectteams.Team is the root of all team classes. It is implicitly taken as the super-class of any team class that has no explicit extends clause. If an explicit extends clause is present in a team, the super-class must again be a team class. Conversely, any subclass of a team (including org.objectteams.Team) must again be a team. Interface implementation is not affected by this rule.

Infrastructure provided by class org.objectteams.Team is presented in  §6.

§1.3.1. Acquisition and implicit inheritance of role classes

A team acquires all roles from its super-team. This relation is similar to inheritance of inner classes, but with a few decisive differences as defined next. Two implementation options are mentioned below, which can be used to realize the special semantics of role acquisition (virtual classes and copy inheritance).
1
 public team class S {
2
   protected class R0 {...}
3
   protected class R1 extends R0 {
4
     boolean ok;
5
     R2 m() {...}
6
     void n(R2 r) {...}
7
   }
8
 }
9
   protected class R2 {...}
10
 }

11
 public team class T extends S {
12
   protected class R1 {
13
     R2 m() {
14
       if(ok) { return tsuper.m(); }
15
       else { return null; }
16
     }
17
     void doIt() {
18
       n(m());
19
     }
20
   }
21
 }
(a) Role class acquisition
A team T which extends a super-team S has one role class T.R corresponding to each role S.R of the super-team. The new type T.R overrides R for the context of T and its roles. Acquisition of role classes can either be direct (see (b) below), or it may involve overriding and implicit inheritance ((c) below).

In the above example the team S operates on types S.R0, S.R1 and S.R2, while T operates on types T.R0, T.R1 and T.R2.

(b) Direct role acquisition
Within a sub-team T each role S.R of its super-team S is available by the name T.R without further declaration. In the lexical context of T also the short name R refers to the role T.R.

The role R2 can be used in the sub-team T (line 13), because this role type is defined in the super class of the enclosing team.

(c) Overriding and implicit inheritance
If a team contains a role class definition by the same name as a role defined in its super-team, the new role class overrides the corresponding role from the super-team and implicitly inherits all of its features. Such relation is established only by name correspondence.
It is an error to override a role class with an interface or vice versa. A final role cannot be overridden.
Unlike regular inheritance, constructors are also inherited along implicit inheritance, and can be overridden just like normal methods.

R1 in T implicitly inherits all features of R1 in S. This is, because its enclosing team T extends the team S (line 11) and the role definition uses the same name R1 (line 12). Hence the attribute ok is available in the method m() in T.R1 (line 14).

(d) Lack of subtyping
Direct acquisition of roles from a super-team and implicit inheritance do not establish a subtype relation. A role of a given team is never conform (i.e., substitutable) to any role of any other team. S.R and T.R are always incommensurable.
Note, that this rule is a direct consequence of §1.2.2(e).
(e) Dynamic binding of types
Overriding an acquired role by a new role class has the following implication: If an expression or declaration, which is evaluated on behalf of an instance of team T or one of its contained roles, refers to a role R, R will always resolve to T.R even if R was introduced in a super-team of T and even if the specific line of code was inherited from a super-team or one of its roles. Only the dynamic type of the enclosing team-instance is used to determine the correct role class (see below for an example).

A special case of dynamically binding role types relates to so-called class literals (see JLS 15.8.2). Role class literals are covered in §6.1(c).

The above is strictly needed only for cases involving implicit inheritance. It may, however, help intuition, to also consider the directly acquired role T.R in (b) to override the given role S.R.

In line 18 the implicitly inherited method n is called with the result of an invocation of m. Although n was defined in S (thus with argument type S.R2, see line 6) in the context of T it expects an argument of T.R2. This is correctly provided by the invocation of m in the context of T.

(f) tsuper
Super calls along implicit inheritance use the new keyword tsuper. While super is still available along regular inheritance, a call tsuper.m() selects the version of m of the corresponding role acquired from the super-team.

See § 2.4.2 for tsuper in the context of role constructors.

tsuper can only be used to invoke a corresponding version of the enclosing method or constructor, i.e., an expression tsuper.m() may only occur within the method m with both methods having the same signature (see §2.3.2(b) for an exception, where both methods have slightly different signatures).

R1 in T overrides the implicitly inherited method m() from S.
tsuper.m() calls the overridden method m() from S.R1 (line 14).

(g) Implicitly inheriting super-types
If a role class has an explicit super class (using extends) this relation is inherited along implicit inheritance.

The role R1 in T has T.R0 as its implicitly inherited super class, because the corresponding role in the super-team extends R0 (line 3).

Overriding an implicitly inherited super class is governed by §1.3.2b, below.
The list of implemented interfaces is merged along implicit inheritance.
(h) Preserving visibility
A role class must provide at least as much access as the implicit super role, or a compile-time error occurs (this is in analogy to JLS 8.4.6.3). Access rights of methods overridden by implicit inheritance follow the same rules as for normal overriding.
(i) Dynamic binding of constructors
When creating a role instance using new not only the type to instantiate is bound dynamically (cf. (e)), but also the constructor to invoke is dynamically bound in accordance to the concrete type.
Within role constructors all this(..) and super(..) calls are, however, bound statically with respect to explicit and implicit inheritance.
See also §2.5(a) on using constructors of abstract role classes.
(j) Overriding and compatibility
The rules of JLS 8.4.6 also apply to methods and constructors inherited via implicit inheritance.

Example code ("Teams and Roles"):

1
 public team class MyTeamA {
2
   protected class MyRole {
3
     String name;
4
     public MyRole (String n) { name = n; }
5
     public void print()     { System.out.println("id="+name); }
6
   }
7
   public MyRole getRole()  { return new MyRole("Joe"); }
8
 }
9

10
 public team class MySubTeam extends MyTeamA {
11
   protected class MyRole {
12
     int age;
13
     public void setAge(int a) { age = a; }
14
     public void print() { 
15
       tsuper.print();
16
       System.out.println("age="+age);
17
     }
18
   }
19
   public void doit() {
20
     MyRole r = getRole();
21
     r.setAge(27);
22
     r.print();
23
   }
24
 }
25
 ...
26
 MySubTeam myTeam = new MySubTeam();
27
 myTeam.doit();

Program output

 id=Joe
 age=27

Effects

  • According to §1.3, MyTeamA inherits from Team (line 1).
  • An implicit role inheritance is created for MySubTeam.MyRole (§1.3.1(c); line 11).
    If we visualize this special inheritance using a fictitious keyword overrides the compiler would see a declaration:
      protected class MyRole overrides MyTeamA.MyRole { ... }
  • Invoking getRole() on myTeam (line 27, 20) creates an instance of MySubTeam.MyRole because the acquired role MyTeamA.MyRole is overridden by MySubTeam.MyRole following the rules of implicit inheritance (cf. §1.3.1(e)).
  • Overriding of role methods and access to inherited features works as usual.
  • As an example for §1.3.1(f) see the call tsuper.print() (line 15), which selects the implementation of MyTeamA.MyRole.print.

§1.3.2. Regular role inheritance

In addition to implicit inheritance, roles may also inherit using the standard Java keyword extends. These restrictions apply:
(a) Super-class restrictions
If the super-class of a role is again a role it must be a role of an enclosing team.
This rule is simply enforced by disallowing type anchors in the extends clause (see §1.2.2(g)).
As an effect, the super class may only see fewer enclosing instances, never more.
(b) Inheriting and overriding the extends clause
If a role overrides another role by implicit inheritance, it may change the inherited extends clause (see 1.3.1(g) above) only if the new super-class is a sub-class of the class in the overridden extends clause. Ie., an implicit sub-role may specialize the extends clause of its implicit super-role.
(c) Constructors and overridden 'extends'
Each constructor of a role class that overrides the extends clause of its implicit super-role must invoke a constructor of this newly introduced explicit super-class. Thus it may not use a tsuper constructor (see §2.4.2).
(d) Adding implemented interfaces
implements declarations are additive, i.e., an implicit sub-role may add more interfaces but has to implement all interfaces of its implicit super-role, too.

§1.4. Name clashes

ObjectTeams/Java restricts Java with respect to handling of conflicting names.
(a) Names of role classes
A role class may not have the same name as a method or field of its enclosing team. A role class may not shadow another class that is visible in the scope of the enclosing team.
(b) Names of role methods and fields
Along implicit inheritance, the names of methods or fields may not hide, shadow or obscure any previously visible name.
(see JLS 8.3, 8.4.6.2, 8.5, 9.3, 9.5 (hiding), 6.3.1 (shadowing), 6.3.2 (obscuring).

§1.5. Team and role nesting

Multi-level nesting of classes is restricted by the following rules.
1
 public team class SuperOuter {
2
   public team class RoleAndTeam {
3
      protected class InnerRole { 
4
         Runnable foo() { return null; }
5
      }
6
   }
7
   public team class RoleAndTeamSub extends RoleAndTeam {
8
      protected class InnerRole { 
9
         Runnable foo() { throw new RuntimeException(); }
10
      }
11
   }
12
}
13
 public team class OuterTeam extends SuperOuter {
14
   public team class RoleAndTeam {
15
      protected class InnerRole { 
16
         Runnable foo() {
17
            class Local {};
18
            return new Runnable() { // anonymous class definition
19
                public void run() {}
20
            };
21
         }
22
         // class IllegalMember {}
23
      }
24
   }
25
   public team class RoleAndTeamSub  {
26
      protected class InnerRole { 
27
         Runnable foo() { 
28
            RoleAndTeamSub.tsuper.foo();
29
            return OuterTeam.tsuper.foo();
30
          };
31
      }
32
   }
33
}
(a) Nested teams
If a role class is also marked using the team modifier, it may contain roles at the next level of nesting. Such a hybrid role-and-team has all properties of both kinds of classes.
(b) Nested classes of roles
A regular role class (ie., not marked as team, see above) may contain local types (see JLS 14.3 - in the example: class Local), anonymous types (JLS 15.9.5 - in the example: class defined in lines 18-20) but no member types (JLS 8.5 - in the example: illegal class IllegalMember).
The effect is, that nested types of a regular role cannot be used outside the scope of their enclosing role.
(c) Prohibition of cycles
A nested team may not extend its own enclosing team.
(d) Prohibition of name clashes
A nested team may inherit roles from multiple sources: its explicit super team and any of its implicit super classes (roles) from different levels of nesting. If from different sources a team inherits two or more roles of the same name that are not related by implicit inheritance, this is an illegal name clash.
(e) Precedence among different supers
If a role inherits the same feature from several super roles (super and tsuper), an implicitly inherited version always overrides any explicitly inherited feature.
Also implicit inheritance alone may produce several candidate methods inherited by a role class. This is a result of team-nesting where each level of nesting may add one more tsuper role. In that case inner team inheritance has precedence over outer team inheritance.
(f) Qualified tsuper
A role in a nested team may qualify the keyword tsuper (see §1.3.1(f) above) by a type name in order to select among different method version inherited from different implicit super classes. A term OuterTeam.tsuper evaluates to the super-class, say SuperOuter, of an enclosing team "OuterTeam". A method call OuterTeam.tsuper.m() evaluates to the method version within SuperOuter that best corresponds to the current method containing the tsuper-call.

Role acquisition and implicit inheritance can be implemented in at least two ways.

Virtual classes: Each role class is an overridable feature of its enclosing team. Role classes are resolved by dynamic binding with respect to the enclosing team instance. This implementation requires multiple-inheritance in order to also allow regular inheritance between roles of the same team. super and tsuper select parent versions of a method along the two dimensions of inheritance.

Copy inheritance: Role acquisition from a super-team has the effect of copying a role definition T.R yielding a new role Tsub.R. All role applications Rx in the role copy refer to Tsub.Rx. Implicit role inheritance extends a role copy in-place. Only the tsuper construct allows to access the previous version of a method (i.e. before in-place overriding).

© Stephan Herrmann, Christine Hundt
OT/J Version 0.9 — Last modified: Wed Dec 20 2006