§3.1. Callout method binding
A role class may acquire the implementation for any of its
(expected) methods by declaring a
callout binding.
(a)
Prerequisite: Class binding
A callout binding requires the enclosing class to be a role class
bound to a base class according to
§2.1.
(b)
Definition
A callout binding maps an abstract role method ("expected method")
to a concrete base method ("provided method").
It may appear within the role class at any place where feature
declarations are allowed. It is denoted by
expected_method_designator "->"
provided_method_designator;
The effect is that any call to the role method will be forwarded to the
associated base object using the provided base method.
Example code:
| 1 |
team class Company { |
| 2 |
public class Employee playedBy Person { |
| 3 |
abstract String getIdentification(); |
| 4 |
// callout binding see below... |
| 5 |
} |
| 6 |
} |
|
(c)
Kinds of method designators
A method designator may either be a method name
| 4 |
getIdentification -> getName; |
|
or
a complete method signature including parameter declarations and
return type declaration, but excluding any modifiers.
| 4 |
String getIdentification() -> String getName(); |
|
Both sides of a callout binding must use the same kind of
designators, i.e., designators with and without signature
may not be mixed.
Each method designator must uniquely select one method.
For this selection
method signatures must match exactly without any implicit conversions.
If overloading is involved, signatures must be used to disambiguate.
(d)
Inheritance of role method declarations
The role method being bound by a callout may be declared in the same
class as the binding or it may be inherited from a super class or
super interface.
(e)
Callout override
If an inherited role method is concrete, callout binding regarding this
method must use the token "=>" instead of "->"
in order to declare that this binding overrides an existing implementation.
Using the "=>" operator for an abstract method is an error.
It is also an error (and not useful anyway) to callout-bind a method that is
implemented in the same class as the binding.
(f)
Inheritance of callout bindings
Callout bindings are inherited along explicit and implicit inheritance.
Inherited callout bindings can be overridden using "=>".
(g)
Duplicate bindings
It is an error if a role class has multiple callout bindings for the
same role method.
(h)
Declared Exceptions
It is an error if a base method to be bound by callout
declares in its throws clause any exceptions that
are not declared by the corresponding role method.
(i)
Shorthand definition
A callout binding whose method designators specify
full method signatures does not require an existing
role method.
If no role method is found matching the expected method of
such a callout binding,
a new method is implicitly generated.
The new method has default visibility and none of the
possible modifiers for methods are set. The new method declares the same
exceptions as the bound base method.
A callout binding either attaches an implementation to a previously declared method or adds (
3.1(i) above)
a forwarding method to a role class.
Apart from this implementation, callout-bound methods do not
differ from regular methods.
When we say, a callout binding defines
forwarding this means that
control is passed to the base object. In contrast, by a
delegation
semantics control
would remain at the role object, such that self-calls
would again be dispatched starting at the role. Callout bindings on
their own do not support delegation. However, in conjunction with method
overriding by means of callin bindings (see
§4.)
the effect of delegation can easily be achieved.
§3.2. Callout parameter mapping
(a)
with clause
If the method designators in a callout binding are signatures
(not just method names), parameters and return value may
be mapped by a with{...} sub-clause.
(b)
Mapping one parameter
For each parameter of the provided base method, exactly one parameter
mapping defines, which value will actually be passed to
the base method. Callout parameter mappings have this form:
expression->base_method_parameter_name
(c)
Result mapping
The return value of a callout method may be provided by a result mapping:
"result" "<-" expression
The right hand side expression of a result mapping may use the special identifier
result to refer to the value returned by the base method.
In a method binding with parameter mappings, it is an error to use
result as the name of a regular method argument.
Example code:
| 1 |
Integer absoluteValue(Integer integer) -> int abs(int i) with { |
| 2 |
integer.intValue() -> i, |
| 3 |
result <- new Integer(result) |
| 4 |
} |
|
(d)
Visible names
Each identifier that appears within the expressions of a parameter
mapping must be either:
- a feature visible in the scope of the role instance.
- a parameter of the role method (for parameter mappings).
- the special name
result (for result mappings).
The names of base method arguments (i.e., names after mapping) are only
legal in the position given in
§3.2(b).
(e)
Implicit parameter mappings
If parameter mappings should be omitted the following conditions must hold:
- each method parameter of the role method must conform to the
corresponding parameter of the base method, and
- the result type of the base method must conform to the result type
of the role method.
Here conformance includes translation polymorphism
(cf.
§3.3(d)).
Parameter correspondence without parameter mapping is determined by declaration
order not by names.
Two adjustments can, however, be performed implicitly:
- If the role method has more parameters than the base method,
unused trailing parameters may be silently ignored.
- If the role method returns
void, any result from
the base method may be silently ignored.
Example code:
| 1 |
public team class MyTeamA { |
| 2 |
public class abstract Role1 { |
| 3 |
abstract void payEuro(float euro); |
| 4 |
abstract float earnEuro(); |
| 5 |
void idle(int seconds) { /* do nothing */ }; |
| 6 |
} |
| 7 |
Role1 boss, worker = // initialization omitted |
| 8 |
public void transaction () { |
| 9 |
boss.payEuro(worker.earnEuro()); |
| 10 |
boss.idle(123); |
| 11 |
} |
| 12 |
} |
| 13 |
public class Staff { // a base class
|
| 14 |
public void payDM (float dm) { … }; |
| 15 |
public float earnDM () { … }; |
| 16 |
public int doze() { … }; |
| 17 |
// other methods omitted |
| 18 |
} |
| 19 |
public team class MySubTeam extends MyTeamA { |
| 20 |
public class Role1 playedBy Staff { |
| 21 |
void payEuro(float euro) -> void payDM(float dm) with { |
| 22 |
euro * 1.95338f -> dm |
| 23 |
} |
| 24 |
float earnEuro() -> float earnDM () with { |
| 25 |
result <- result / 1.95338f |
| 26 |
} |
| 27 |
idle => doze // override existing implementation of idle() |
| 28 |
} |
| 29 |
void doit() { |
| 30 |
transaction(); |
| 31 |
} |
| 32 |
} |
|
Effects
§3.3. Lifting and lowering
(For basic definitions see
§2.2
and
§2.3)
(a)
Call target translation
Invoking a base method due to a callout binding first
lowers the role object in order to obtain the effective call target.
(b)
Parameter translation
Passing a role object as parameter to a callout method implicitly lowers
this parameter.
Lifting of callout parameters is not possible.
(c)
Result translation
When returning a base object from a callout method where the role
method declares the result to be of a role class,
this object is implicitly lifted to the appropriate role.
Lowering the result of a callout binding is not possible.
(d)
Typing rules
A parameter mapping (implicit by parameter position or explicit
by a
with clause) is
well typed if
the left hand side conforms to the right hand side, either by
- type equality
- implicit primitive type conversion
- subtype polymorphism
- translation polymorphism, here: lowering,
- or by a combination of the above.
A result mapping (implicit or explicit by a
with clause)
is well typed, if the value at the right hand side conforms to the
left hand side according to the rules given above, except that
translation polymorphism here applies
lifting instead of
lowering.
(e)
Role arrays
For arrays of roles as parameters
§2.2(e) applies accordingly.
For arrays as a return value
§2.3(d) applies.
§3.4. Overriding access restrictions
In contrast to normal access restrictions, method bindings
may refer to hidden base methods.
This concept is the inverse of encapsulation, hence it is called
decapsulation.
Decapsulation may occur in these positions:
playedBy declaration (see §2.1.2(c))
- base constructor call (see §2.4.2(b)).
- callout bindings (see next)
- callout to field (see §3.5(e))
- basecall within a callin method (see §4.6)
(a)
Callout to inaccessible base method
By means of callout bindings it is possible to access
methods of a base class regardless of their access modifiers.
Method bindings are the only place in a program which may mention otherwise
inaccessible methods.
Access to the callout method at the role side is controlled by regular mechanisms,
based on the declaration of the role method.
(b)
Sealing against decapsulation
A base package may be "sealed" which re-establishes the
standard Java visibility rules.
Sealing is achieved by the corresponding capability of Jar files.
(c)
Warning levels
By means of compiler options three levels of verbosity
can be selected with respect to decapsulation:
| -nodecapsulation |
No warnings. |
| default |
Warn only if/that access restrictions are overridden. |
| -decapsulation |
Detailed messages containing the
binding and the hidden base method.
|
If a callout 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. I.e., for private methods
§ 3.1(d) does not hold.
The same holds for private base fields (see below).
If a private base feature must indeed be callout-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
callout binding and through this it can access the desired feature.
| 1 |
public class SuperBase { |
| 2 |
private int secret; |
| 3 |
} |
| 4 |
public class SubBase extends SuperBase { /* details omitted */ } |
| 5 |
public team class MyTeam { |
| 6 |
protected class SuperRole playedBy SuperBase { |
| 7 |
int steal() -> get int secret; // OK |
| 8 |
} |
| 9 |
protected class SubRole extends SuperRole playedBy SubBase { |
| 10 |
int steal() -> get int secret; // illegal! |
| 11 |
} |
| 12 |
} |
|
§3.5. Callout to field
Also fields of a base class can be made accessible using a callout binding.
(a)
Syntax
Using one of the callout modifiers
get or
set a role method
can be bound to a field of the role's base class:
| 1 |
getValue -> get value; |
| 2 |
setValue -> set value; |
| 3 |
int getValue() -> get int value; |
|
where
getValue,
setValue are abstract role methods of
appropriate signatures and
value is a field of the bound base class.
A longer syntax is available, too (see line 3 above), which uses complete signatures.
For the left hand side
§3.1(c) applies, for the
right hand side, this longer version prepends the field type to the field name.
(b)
Compatibility
A role method bound with the modifier get
should have no arguments
(it may have arbitrary arguments, which are silently ignored)
and should have a return type to which the base field is compatible.
A role method returning void will ignore the given value and thus has
no effect at all, which will be signaled by a compiler warning.
A role method bound with the modifier set must have a first argument
that is compatible to the base field's type (additional arguments - if present -
are silently ignored) and must not declare a return type.
(c)
Value mapping
Values can be mapped similar to parameter mappings in pure method bindings
(
§3.2). Such mappings can be used to establish compatibility as required above.
In both
get and
set bindings, the base side value is denoted by the field's name (lines 2 and 4 below).
| 1 |
Integer getValue() -> get int val |
| 2 |
with { result <- new Integer(val) } |
| 3 |
void setValue(Integer i) -> set int val |
| 4 |
with { i.intValue() -> val } |
|
(d)
Effect
Callout-binding a role method to a base field generates an implementation for
this role method, by which it acts as a getter or setter for the given
field of the associated base object.
(e)
Access control
For accessing an otherwise invisible field, the rules for
decapsulation (§3.4) apply accordingly.
Recall, that according to
JLS 8.3 fields may be hidden in sub-classes of a given base class. Therefore, it is relevant to know that a callout to
a field will always access the field that is visible in the exact base
class of the role class defining the callout. This is especially relevant
for accessing private fields.
(f)
Shorthand definition
Just as in
§3.1(i) a shorthand definition allows to introduce a callout field access method without prior abstract declaration. This requires
the callout field binding to specify types as in line 3 of
(a) "Syntax" above.
(g)
Callout override
Similar to method callouts a callout to field may override an existing role
method if and only if the token
=> is used instead of
->
(see
§3.1(e) and
§3.1(f)).