Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

OTJ Primer/Role Containment

< OTJ Primer
Revision as of 12:41, 8 July 2010 by Stephan.cs.tu-berlin.de (Talk | contribs) (Data Flows)

Role containment is a superior version of class containment in Java.

RoleContainment.png

Just like role playing, role containment is a relation that is declared between classes but essentially affects run time instances:

public team class Company {
    public class Employee { /* role details omitted */ }
    /* team details omitted */
}

Just as with inner classes in Java, such nesting of classes implies a connection between instances:

  • creating an inner instance requires an outer instance to be in scope, either:
    • within a non-static method of Company: writing new Employee() implicitly connects the new inner instance to the current this of type Company.
    • creating a role outside its team (which is actually discouraged in OT/J) can either use
      • the Java syntax thatCompany.new Employee(), or
      • the OT/J syntax new Employee<@thatCompany>()
The two forms are semantically equivalent and reasons for the second form a outside the scope of this text.
  • as a result of the rules for role instantiation, each role instance has an immutable non-null reference to its enclosing team, accessible using the Java syntax Company.this within the role

Role Visibility

So far, roles behave just like inner classes. Next the developer of a role class must decide whether a role should be public or protected (there are no other choices).

Protected roles

It should be the normal case that roles are protected. This gives a team full control over the role. Only the team and sibling roles will be able to communicate with a protected role. To be more precise, it is the team instance that owns a role and acts as a fassade to it. So, the following method call, even when it occurs in a method of Company, is illegal if Employee is protected:

Company otherCompany = ...
Employee leader = otherCompany.getTeamLeader();

The compiler will complain about an illegal attempt to "externalize" (OTJLD §1.2.2) a protected role. Specifically, if getTeamLeader() returns an object of type Employee, this method can only be called with this as the receiver, ensuring that nobody outside the owning team instance will ever get a reference to the team's role instances.

Further reading: with just the rules above an Employee instance could still leak out off the team by upcasting the role to some non-role supertype, ultimately to Object. The new top-level superclass Team.Confined provides means to prohibit upcasting even to Object (see OTJLD §7.2).

Public roles

By declaring a role public a pre-condition is met that instance of this type can actually be seen outside the enclosing team instance. Howeverm the type rules for "externalized roles" (OTJLD §1.2.2) still distinguish, e.g., the types Employee<@microsoft> and Employee<@canonical>, and consider both types as incompatible (in this example, microsoft and canonical are assumed to be instances of Company).

The usage of "externalized roles" using type syntax like Employee<@canonical> is actually not recommended for the novice. If, despite all recommendations, a role shall still be accessed from the outside of the team, it might be a good idea to simply declare an interface outside the team, let the role implement the interface and access the role via its non-role interface:

public interface IEmployee { String getName(); }
public team class Company {
    protected class Employee implements IEmployee {
        getName -> getName; // declaration for this method is inherited from IEmployee
    }
    private Employee leader;
    public IEmployee getTeamLeader() {
        return this.leader;
    }
}
...
    Company thatCompany = ...
    IEmployee leader = thatCompany.getTeamLeader();
    System.out.println(leader.getName());

With this design we clearly distinguish which properties of a role should be visible from the outside.

Further reading: a severe restriction of inner classes in Java is the direct coupling of logical nesting and physical file structure. This simply doesn't scale for many tens of inner classes and perhaps nesting at more than two levels. To overcome these issues, OT/J supports an alternative file structure using "team packages" and "role files" (OTJLD §1.2.5).

Data Flows

Given that roles are normally protected, i.e., confined within their enclosing team instance, the question arises how information can be exchanged between a team and the outside. This is when the playedBy relation comes back into focus.

TeamRoleBase.png

Role-to-base

Given that a role and its base are two sides of the same coin, the compiler can easily help with a little translation:

public team class Company {
    protected class Employee playedBy Person {}
    private Employee leader;
    public Person getTeamLeader() {
        return this.leader;
    }
}
...
    Company thatCompany = ...
    Person leader = thatCompany.getTeamLeader();

Only one thing has changed wrt the previous example: getTeamLader() now declares to return a Person. Well, the actual returned value this.leader isn't exactly of type Person, and Employee actually is not a subtype of Person, but the compiler can easily extract the underlying Person from any Employee instance and return this instead of the role.

This translation from a role instance to its base instance is called lowering. It is applied implicitly in all program locations where a role instance is provided which does not conform to the required type of the expression, but where extracting the base instance actually yields a value of the required type.

Base-to-role

The opposite direction is a bit more challenging: assume you have a base instance of type Person and want to use this as the handle refering to a role. The team could have multiple role types for the same base type, like when we introduce a second role Client playedBy Person to our running example.

In order to help the compiler here, the developer can use the following kind of method signature:

public class Company {
    protected class Employee playedBy Person {
        protected void acceptRequest(String request) { /* */ }
    }
    public void speakToClark(Person as Employee someone, String request) {
        someone.acceptRequest(request);
    }
}

Here the argument someone has a two-sided type: the client has to provide an instance of type Person, but inside the method we want to speak to the Employee role of the person, since method acceptRequest is only defined in Employee. The translation from a base (Person) to a matching role (Employee) is called lifting, which has the following properties:

  • lifting requires three inputs
    • a base instance (explicitly provided by the client)
    • a team instance (implicit in non-static team methods)
    • a role type (here declared after as)
  • if all three inputs are identical across multiple invocations, lifting will always find the same role instance.
  • if lifting finds no existing role matching all three inputs a new role will be created on-demand and internally cached for later use.
  • lifting does not interfere with garbage collection.

Translations in method bindings

The most convenient way to use lifting and lowering is in method bindings.

public class Person {
    public Person getSpouse() { /* */ }
}
public class Company {
    protected class EmployeeSpouse playedBy Person { }
    protected class Employee playedBy Person {
        EmployeeSpouse getSpouse() -> Person getSpouse();
    }
}

Here the callout binding interprets the return value from Person.getSpouse() as an EmployeeSpouse, performing the necessary lifting fully automatic behind the scenes.

This is a summary of how lifting/lowering are supported in method bindings:

kind return argument
callout lifting lowering
callin lowering lifting


Note.png
Disjoint but connected worlds
Using protected roles and lifting/lowering translations it is possible to develop a team such that it is a self-contained world, implemented purly in terms of its own roles. Conversely, any base classes only refer to other base classes. Still both worlds can communicate by way of the implicit object mapping realized as lifting and lowering.


Team Activation

Given that method call interception (by means of callin) is a significant intervention into an existing class, it is important to precisely control when a callin binding should have an effect.

Consider the Person joe being asked for his phone number. Answering the office phone number only makes sense when joe is actually in the office so he will here the office phone ringing. In Object Teams we use team activation to represent a given context in which the program should perform its behavior.

TeamActivation.png

Team activation is a feature of team instances. So the easiest way to activate a context is

Person joe = ...
String joesOfficePhone;
Company canonical = ...
canonical.activate();
joesOfficePhone = joe.getPhoneNo();
canonical.deactivate();

The methods activate and deactivate are declared in the implicit super-type of all teams, org.objectteams.ITeam.

Variants of team activation include

  • per-thread vs. global (all-threads) activation OTJLD §5.1.1
  • method call (activate) vs. control block (within OTJLD §5.2.a)
  • explicit vs. implicit activation (whenever the controlflow enters a team, OTJLD §5.3).
  • in-program activation vs. deployment-time activation OTJLD §5.5

Callin-enablement can be further restricted by guard predicates OTJLD §5.4. Guard predicates are particularly useful for the Object Registration pattern, which ensures that only existing roles will be considered for callin dispatch.

Back to the top