| << §3 Callout Binding | ↑ Table of Contents ↑ | §5 Team Activation >> |
Callin bindings realize a forwarding in the direction opposite to callout bindings (see §3). Both terms are chosen from the perspective of a role, which controls its communication with an associated base object. Technically, callin bindings are equivalent to weaving additional code (triggers) into existing base methods.
A role method may intercept calls to a base method by a callin binding.
A callin binding requires the enclosing class to be a role class
bound to a base class according to §2.1.
An unliftable role (see §2.3.4.(a)) cannot define
callin bindings. In that case callin bindings can only be introduced in sub-roles
which (by an appropriately refined playedBy clause) disambiguate the lifting translation.
A callin binding composes an existing role method with a given base method. It may appear within the role class at any place where feature declarations are allowed. It is denoted by
role_method_designator <- callin_modifier base_method_designator;
Just like with callout bindings, method designators may
or may not contain parameters lists and return type but no modifiers;
also, each method designator must exactly and uniquely select one method
(cf. §3.1.(c)).
For callin modifiers see below (§4.2).
Base method designators may furthermore enumerate a
list of methods. If multiple base methods are bound in one
callin declaration generally all signatures in this binding must be conform.
However, extraneous parameters from base methods may be ignored
at the role.
For result types different rules exist, depending on the
applied callin modifier (see next).
Any callin binding may be labeled with a name. The name of a callin binding is used
for declaring precedence (§4.8).
A named callin binding overrides any inherited callin binding
(explicit and implicit (§1.3.1)) with the same name.
It is an error to use the same callin name more than once within the same role class.
When binding to a final base method, the enclosing role must be played by the exact base class declaring the final method. I.e., callin binding to a final method inherited from the base class's super-class is not allowed. This is motivated by the fact that no sub-class may have a version of a final method with different semantics.
It is an error if a role method to be bound by callin declares in its throws clause any exceptions that are not declared by the corresponding base method(s).
The kind of method composition is controlled by adding one
of the modifiers before, after or replace after the
"<-" token of the binding declaration.
The before and after modifiers have the
effect of adding a call to the role method at the beginning or end
of the base method, resp.
In this case no data are transferred from the role to the base,
so if the role method has a result, this will always be ignored.
| 1 | team class Company { |
| 2 | protected class Employee playedBy Person { |
| 3 | public void recalculateIncome() { ... } |
| 4 | recalculateIncome <- after haveBirthday; // callin binding |
| 5 | } |
| 6 | } |
The replace modifier causes only the role method to be
invoked, replacing the base method.
In this case, if the base method declares a result, this should be provided by the role method.
Special cases of return values in callin bindings are discussed in §4.3.(e)
Role methods to be bound by a callin replacement binding must have
the modifier callin. This modifier is only allowed for methods
of a role class.
A method with the callin modifier can only be called
super or tsuper call from an overriding callin method.
It is illegal for a callin method
before or afterA callin method cannot override a regular method and vice versa, however,
overriding one callin method with another callin method is legal and
dynamic binding applies to callin method just like regular methods.
A callin method must not declare its visibility using any of the modifiers public,
protected or private. Since callin methods can only be invoked via callin bindings
such visibility control would not be useful.
Role methods with a callin modifier should contain
a base call which uses the special name base
in order to invoke the original base method (original means: before replacement).
The syntax for base calls is base.m(),
which is in analogy to super calls.
A base.m() call must use the same name and signature
as the enclosing method. This again follows the rule, that roles
should never explicitly use base names, except in binding declarations.
For each callin method, the compiler uses some flow analysis to check whether
a base call will be invoked on each path of execution (analysis is very similar
to the analysis for definite assignment regarding final variables -
JLS §16).
The compiler will issue a warning if a base call is missing either
on each path (definitely missing) or on some paths (potentially missing).
Instead of directly invoking a base call, a callin method may also call
its explicit or implicit super version using super.m() or tsuper.m() (see §1.3.1.(f)).
In this case the flow analysis will transitively include the called super/tsuper version.
If a callin method contains several base calls, the compiler gives a warning if this will result in duplicate base call invocations on all paths (definitely duplicate) or on some paths (potentially duplicate). Again super/tsuper calls are included in the flow analysis (see 4.3(b)).
If a base method has more parameters
than a callin method to which it is composed, additional
parameters are implicitly passed unchanged from the original
call to the base call (original means: before interception).
I.e., a call base.m() may invisibly pass additional
parameters that were provided by the caller, but are hidden from the
role method.
If a role method returns void, but the bound base method declares a non-void result, this is reported as a fragile callin binding: The result can still be provided by the base call, but omitting the base call may cause problems depending on the return type:
null will be returned
in this case.
ResultNotProvidedException at run-time.
It is an error if a callin method involved in a fragile callin binding has definitely no base call.
| 1 | public class ValidatorRole playedBy Point { |
| 2 | callin void checkCoordinate(int value) { |
| 3 | if (value < 0) |
| 4 | base.checkCoordinate(-value); |
| 5 | else |
| 6 | base.checkCoordinate(value); |
| 7 | } |
| 8 | checkCoordinate <- replace setX, setY; |
| 9 | } |
Point (see line 8).
The rules for mapping callin parameters and result type
are mainly the same as for callout bindings (§3.2)
except for reversing the -> and <- tokens and
swapping left hand side and right hand side.
Callin bindings using before have no result mapping.
For result in after callin bindings
see §4.4.(c) below.
The right-hand side of a parameter mapping may either be
the simple name of a base method argument without further computation,
or an arbitrary expression not containing any base method argument.
Each base method argument must either appear as a simple name in exactly one parameter mapping
or not be mapped at all. In the latter case, the original argument is "tunneled" to
the base call, meaning, the callin method does not see the argument, but it is passed
to the base method as expected.
If the base method declares a result, then
result must be mapped to itself:
result -> resultexpression -> resultThese rules ensure that these bindings are reversible for the sake of base calls (§4.3).
As stated above a fragile callin binding (§4.3.(e)) is not allowed with a callin method that definitely has no base call (§4.3.(b)). A callin replace binding is not fragile if it provides the base result using a result mapping.
A callin method bound with replace to a base method returning void must not declare a non-void result.
In an after callin binding, the right-hand side
of a parameter mapping may use the identifier result
to refer to the result of the base method.
A callin binding listing more than one base method may use parameter mappings with only the following restriction: if any base parameter should be mapped this parameter must have the same name and type in all listed base method designators. However, different parameter mappings for different base methods bound to the same role method can be defined if separate callin bindings are used.
For basic definition see §2.2
and §2.3.
(The following rules are reverse forms of those from §3.3)
Invoking a role method due to a callin binding first lifts the base object to the role class of the callin binding, in order to obtain the effective call target. This is why callin bindings cannot be defined in roles that are unliftable due to potential binding ambiguity (see §4.1.(b) above and §2.3.4.(a)).
During callin execution, each parameter for which the role method expects a role object is implicitly lifted to the declared role class.
Returning a role object from a callin method implicitly lowers this object.
A parameter mapping (implicit by parameter position or explicit
by a with clause) is well typed if
the right hand side conforms to the left hand side, either by
replace bindings step 1 of the smart lifting
algorithm (§2.3.3.(a)) is not applicable
A result mapping (implicit or explicit by a with clause)
is well typed, if the value at the left hand conforms to the
right hand side according to the rules given above, except that
translation polymorphism here applies lowering instead of
lifting.
These rules define translation polymorphism as introduced in §2.3.
Additionally, in a replace callin binding compatibility of parameters and return
types must hold in both directions.
Thus, from the above list of conversions a replace binding cannot apply subtype polymorphism
nor primitive type conversion.
If more flexibility is desired, type parameters can be used as defined in §4.10.
For arrays of roles as parameters §2.3.(d) applies accordingly. For arrays as return value §2.2.(e) applies.
For base calls these rules are reversed again, i.e., a base call behaves like a callout binding.
Callin bindings may also mention inaccessible methods
(cf. decapsulation §3.4).
Due to the reverse call direction this is relevant only for base calls within
callin methods.
Base calls have unrestricted access to protected base methods.
Accessing a base method with private or default visibility is
also allowed, but signaled by a compiler warning.
(Cf. §3.4.(d))
If a callin binding shall bind to a private base method, that method
must be defined in the exact base class to which the current role
class is bound using playedBy.
If a private base feature must indeed be callin-bound, a role class
must be defined that is played by the exact base class defining the
private feature. Another role bound to a sub-base-class can then
be defined as a sub class of the first role. It will inherit the
callin binding and through this it can access the desired feature.
The normal case of callin bindings refers to non-static methods on both sides (base and role). Furthermore, in Java inner classes can not define static methods. Both restrictions are relaxed by the following rules:
A role class may define static methods (see also §1.2.1.(f)).
A callin binding may bind a static role method to one or more static base methods. It is, however, an error to bind a static base method to a non-static role method, because such binding would require to lift a base object that is not provided.
In addition to the above, before and after
callin bindings may also bind a static role method to non-static base methods.
In contrast to §4.7.(c) above, a replace callin binding
cannot bind a static role method to a non-static base method.
The following table summarizes the combinations defined above:
| <- | base method | ||
| static | non-static | ||
| role method |
static | OK | before/after: OKreplace: illegal |
| non-static | illegal | OK | |
Since static methods are not dynamically bound, overriding does not
apply in the normal semantics. Regarding callin bindings this has the
following consequences (assuming a role RMid played by
BMid plus its super-class BSuper and its sub-class
BSub.
BMid.m is bound by a callin binding
this has no effect on any method m in BSub.
m which is not present
in BMid but resolves to a static method in BSuper
the binding only affects invocations as BMid.m() but not
BSuper.m(). If the latter call should be affected, too,
the callin binding must appear in a role class bound to BSuper,
not BMid.
playedBy clause of the other role
(say: public class RSub extends RMid playedBy BSub).
Now each role may bind to the static base method accessible in its direct
base-class.
If multiple callins from the same team refer to the same base method and also have the same
callin modifier (before, after or replace), the order in which
the callin bindings shall be triggered has to be declared using a precedence declaration.
A precedence declaration consists of the keyword precedence followed by
a list of names referring to callin bindings (see §4.1.(e) for named callin bindings).
A precedence declaration is only legal within a role or team class.
Within a role class a callin binding may be referenced by its unqualified name.
A precedence declaration in a team class must qualify the callin name with the name of the declaring
role class. A team with nested teams may concat role class names.
Elements of a qualified callin name are separated by ".".
The callin binding must be found in the role specified by the qualifying prefix or
in the enclosing role for unqualified names, or any super class of this role
(including implicit super classes §1.3.1).
At the team level a precedence declaration may contain role class names without explicitly mentioning callin bindings in order to refer to all callin bindings of the role.
All precedence statements are collected at the outer-most team. At that level all precedence declarations involving the same base method are merged using the C3 algorithm [3]. It is an error to declare incompatible precedence lists that cannot be merged by the C3 algorithm.
Precedence declarations may conflict with overriding of callin bindings
(see §4.1.(e)): For each pair of callin bindings of which one
callin binding overrides the other one, precedence declarations are not applicable,
since dynamic binding will already select exactly one callin binding.
It is an error to explicitly mention such a pair of overriding callin bindings in a precedence declaration.
When a class-based precedence declaration implicitly refers to a callin binding that is overridden by,
or overrides any other callin binding within the same precedence declaration, this does not affect the fact,
that the most specific callin binding overrides less specific ones.
| 1 | public class LogLogin playedBy Database { |
| 2 | callin void log (String what) { |
| 3 | System.out.println("enter " + what); |
| 4 | base.log(what.toLowerCase()); |
| 5 | System.out.println("leave " + what); |
| 6 | } |
| 7 | void log(String what) <- replace void login(String uid, String passwd) |
| 8 | with { what <- uid } |
| 9 | } |
| 10 | (new Database()).login("Admin", "Passwd"); |
Provided the callin bindings are active (cf. §5) then:
log
of role LogLogin.
log is a role of type
LogLogin which is created by lifting the
original call target (of type Database) to
LogLogin.
uid is passed to log
(bound to formal parameter what).
log the base call (line 4) invokes
the original method passing a modified uid (converted to lower case, cf. line 4)
and the unmodified password, which is hidden from the callin method due to the
parameter mapping in line 8.
This section defines how callin bindings and callin methods relate to inheritance.
Generally, a callin binding affects all sub-types of its bound base.
Specifically, if a role type R bound to a base type B
defines a callin binding rm <- callin_modifier bm,
the following rules apply:
The callin binding also effects instances of any type BSub
that is a sub-type of B.
If BSub overrides the bound base method bm,
the overridden version is generally affected, too.
However, if bm covariantly redefines the return type from its
super version, the callin binding has to explicitly specify if the covariant
sub-class version should be affected, too (see §4.9.3.(b)).
The binding never affects an instance of any super-type of B
even if the method bm is inherited from a super-class
or overrides an inherited method.
This ensures that dispatching to a role method due to a callin binding
always provides a base instance that has at least the type declared in the role's
playedBy clause.
For corresponding definitions regarding static methods see §4.7.(e).
Any sub-type of R inherits the given callin binding
(for overriding of bindings see §4.8.(e)).
If the sub-role overrides the role method rm this will be considered
for dynamic dispatch when the callin binding is triggered.
Since version 5, Java supports the covariant redefinition of a method's return type
(see JLS 8.4.5).
This is not supported for callin methods (§4.9.3.(a)).
If base methods with covariant redefinition of the return type are to be bound by a callin binding
the subsequent rules ensure that type safety is preserved.
Two constraints have to be considered:
A method declared with the callin modifier that overrides an inherited method
must not redefine the return type with respect to the inherited method.
This reflects that fact that an inherited callin binding should remain type-safe
while binding to the new, overriding role method.
Binding a covariant role method to the original base method would break constraint (1) above.
If a callin binding should indeed affect not only the specified base method but also overriding versions which covariantly redefine the return type, the binding must specify the base method's return type with a "+" appended to the type name as in
void rm() <- before RT+ bm();
Without the "+" sign the binding would only capture base methods whose
return type is exactly RT;
by appending "+" also sub-types of RT
are accepted as the declared return type.
When using the syntax of §4.9.3.(b) to capture base methods with
covariant return types in a callin binding with the replace modifier,
the role method must be specified using a free type parameter as follows:
<E extends RT> E rm() <- replace RT+ bm();
The role method rm referenced by this callin binding must use the same style
of return type using a type parameter.
The only possible non-null value of type E
to be returned from such method is the value provided by a base-call or a tsuper-call.
This rule enforces the constraint (2) above.
Note that this rule is further generalized in §4.10.
| 1 | public class SuperBase { |
| 2 | SuperBase foo() { return this; } |
| 3 | void check() { System.out.print("OK"); } |
| 4 | } |
| 5 | public class SubBase extends SuperBase { |
| 6 | @Override |
| 7 | SubBase foo() { return this; } |
| 8 | void print() { System.out.print("SubBase"); } |
| 9 | String test() { |
| 10 | this.foo().print(); // print() requires a SubBase |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | public team class MyTeam { |
| 15 | protected class R playedBy SuperBase { |
| 16 | callin <E extends SuperBase> E ci() { |
| 17 | E result= base.ci(); |
| 18 | result.check(); // check() is available on E via type bound SuperBase |
| 19 | return result; |
| 20 | } |
| 21 | <E extends SuperBase> E ci() <- replace SuperBase+ foo(); |
| 22 | } |
| 23 | } |
SubBase.foo in line 7 redefines the return type
from SuperBase (inherited version) to SubBase,
thus clients like the method call in line 10 must be safe to assume
that the return value will always conform to SubBase.
foo
by specifying SuperBase+ as the expected return type.
Thus, if an instance of MyTeam is active at the method call
in line 10, this call to foo will indeed be intercepted
even though this call is statically known to return a value of type SubBase.
E.
Since the base call is known to have the exact same signature as its enclosing
method, the value provided by the base call is of the same type E
and thus can be safely returned from ci.
Note, that no other non-null value is known to have the type E.
SuperBase as an upper bound for the type E
the callin method ci may invoke
any method declared in type SuperBase
on any value of type E. For an example see the call to check
in line 18.
As an aside note that the above example uses type SuperBase
in an undisciplined way: within role R this type is bound
using playedBy and the same type is also
used directly (as the upper bound for E).
This is considered bad style and it is prohibited if SuperBase
is imported using an base import (§2.1.2.(d)).
Here this rule is neglegted just for the purpose of keeping the example small.
As mentioned in §4.5.(d) replace bindings do not support subtype polymorphism
in either direction.
On the other hand, binding several base methods to the same callin method
may require some more flexibility if these base methods have different signatures.
This is where type parameter come to the rescue to allow for generic callin methods
and their binding to base methods with different signatures.
Note that this rule is a generalization of rule §4.9.3.(c).
If a callin method declares a type parameter <T> this type T
can be used for specifying the type of exactly one parameter or the return type.
It is not legal to use the same type parameter in more than one position of a callin method signature.
The type parameter of a callin binding may be bounded by an upper bound as in <T extends C>.
In this case T can only be instantiated by types conforming to the upper bound C.
A generic callin method according to the above rules is bound using a replace
binding that declares the same number of type parameters,
where type parameters of the binding and its callin method are identified.
If the callin method declares bounds for its type parameters
so should the replace binding.
A fresh type parameter can be used to capture arbitrary types in the base methods to be bound. The type parameter may be instantiated differently for each bound base method. By such type parameter instantiation the types in role and base signatures are actually identical, thus satisfying the requirement of two-way substitutability.
Within the body of a generic callin method no further rules have to be followed,
because the fresh type variable actually guarantees, that the role method cannot replace the
original value (initial argument or base-call result) with a different object,
because no type exists that is guaranteed to conform to the type parameters.
Yet, the type bound allows the role method to invoke methods of the provided object.
| 1 | public team class MyTeam { |
| 2 | protected class R playedBy Figures { |
| 3 | callin <E extends Shape, F extends Shape> E ci(F arg) { |
| 4 | E result= base.ci(arg); |
| 5 | result= arg; // illegal, types E and F are incommensurable |
| 6 | arg= result; // illegal, types E and F are incommensurable |
| 7 | int size= arg.getSize(); // getSize() is availabel on F via type bound Shape |
| 8 | result.resize(size); // resize() is available on E via type bound Shape |
| 9 | return result; // only two legal values exist: |
| 10 | } |
| 11 | <E extends Shape, F extends Shape> |
| 12 | E ci(F arg) <- replace Rectangle getBoundingBox(Shape original), |
| 13 | Rectangle stretch(Square original); |
| 14 | } |
| 15 | } |
callin method ci:
Rectangle ci (Shape arg)Rectangle ci (Square arg)callin method the following observations hold:
Shape is not conform to RectangleRectangle is not conform to SquareThe query language for specifying sets of base methods (§4.1.(d)) has not been finalized yet. In this version of the OTJLD §8 acts as a placeholder for the section that will define a join point query language in the future.
[3] Kim Barrett, Bob Cassels, Paul Haahr, David A. Moon, Keith Playford, P. Tucker Withington. A monotonic superclass linearization for Dylan. OOPSLA '96: Proceedings of the 11th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications, pages 69-82, 1996.
| << §3 Callout Binding | ↑ Table of Contents ↑ | §5 Team Activation >> |
Line 4 declares a callin binding for the role method
recalculateIncome()defined in line 3. In combination with the role binding in line 2 this has the following effect:Person.haveBirthdaythe methodCompany.recalculateIncomeis called.