The Language Specification of Epsilon/J Version 0.9 12/1/2004 Tetsuo Tamai Epsilon/J is an extension of Java. It provides a way for describing collaboration fields as "context" where a set of "role"s interact each other to achieve collaborations. A mechanism is provided that allows dynamic binding of an object to a role so that the object acquires the role functions and participates in the collaboration. 1. Context Declaration ContextDeclaration: context Identifier ContextBody A "context" is similar to a class but no modifiers like public, protected and private or abstract, static, final and strictfp is required before the keyword context. It is always public and no other properties including abstract and static apply. Inheritance of other contexts or interfaces are not allowed and thus there are no declaration of Super Context and Interfaces. ContextBody: {ContextBodyDeclarations} ContextBodyDeclarations: ContextBodyDeclaration ContextBodyDeclarations ContextBodyDeclaration ContextBodyDeclaration: ContextMemberDeclaration InstanceInitializer ConstructorDeclaration Since context has the feature of class and can be instantiated, it may declare constructors and initializers. ContextMemberDeclaration: FieldDeclaration MethodDeclaration RoleDeclaration FieldDeclaration and MethodDeclaration are the same as the case of class. Class declarations and interface declarations are not allowed in Context. The unique member decalation is the role declaration. 2. Role 2.1 Role Declaration RoleDeclaration: RoleModifier_opt role Identifier RequiredInterface_opt RoleBody RoleModifier: static Roles are all treated as "public" in the sense they can be bound to any objects that satisfy the interface condition. Thus, modifiers of public, protected and private do not have meaning and other modifiers except "static" are not allowed. When "static" is declared, there is exactly one instance of that role in a context instance and it is created at the time the context instance is created. Note that this semantics of "static" is different from that of Java inner class. In Java, a static class declared in a class is not an inner class; it has no current instance of the enclosing class. On the other hand, a "static" role in a context is associated with its enclosing context instance. It only means the role instance is a singleton in the context. The singleton role instance can be reffered by the role name within the context and by the role name qualified with the context instance reference outside of the context. For example, context C { static role R1 { void m1() { ... ] } role R2 { void m2() { R1.m1(); // invoke the method m1() of R1 in the same context ... } } Context c = new C(); // context instance is created c.R1.m1(); // invoke the method m1() of R1 in c 2.2 Role Instance Creation When a role is not declared "static", its role instances can be created by an indefinite number. A role instance is created using the keyword "new" as usual. For example, context C { static role R1 { void m1() { R2 y = new R2(); //create a role instance of R2 ... ] } role R2 { void m2() { ... } } Context x = new C(); C.R2 y = new x.R2(); /* create a role instance of R2 outside of the context */ As this example shows, a role instance is necessarily associated with the enclosing context instance and thus the constructor should be qualified by a context instance reference, not by context type(i.e. you have to write "new x.R2()" rather than "new X.R2()". A set of instances of a role is called a role group. A role group is associated with a context instance and it is refferred by the role name. There are some methods applied to a role group (see newBind in Section 3.1.1 and also section 4). 2.3 Role as a Type As the example in the last section shows, a role name is also used as a type name. A typical usage of the role name as a type is in the parameter list of a method defined in another role. For example, context C { static role R1 { void m1(R2 r) { /* As R2 is not "static", its role instance has to be designated to invoke its method. */ r.m2(); ... ] } role R2 { void m2() { ... } } 2.4 Required Interface 2.4.1 Required Interface Declaration RequiredInterface has a meaning different from interface inheritance declared by "implements" clause in a Java class. This is an interface the role requires to an object to supply that binds to the role. The binding object should possess all the methods specified in the interface. RequiredInterface: requires InterfaceNameOrBody InterfaceNameOrBody: InterfaceName AnonymousInterface RequiredInterfaceDeclaration: interface InterfaceName ExtendsInterface_opt RequiredInterfaceBody AnonymousInterface: RequiredInterfaceBody RequiredInterfaceBody: {RequiredInterfaceMemberDeclarations_opt} RequiredInterfaceMemeberDeclarations: RequiredInterfaceMemberDeclaration RequiredInterfaceMemberDeclarations RequiredInterfaceMemberDeclaration RequiredInterfaceMemberDeclaration: AbstractMethodDeclaration In short, RequiredInterfaceBody is the same as the normal Java InterfaceBody except that only AbstractMethodDeclaration is allowed and neither of FieldDeclaration, ClassDeclaration and InterfaceDeclaration is allowed. 2.4.2 Interface Method Call Methods specified in the required interface can be called in the definition of role methods. For example, interface Deposit { void deposit(int);} context Company { role Employee requires Deposit { void getSalary(int salary) { deposit(salary);} ... } Anonymous interface is a convenience to avoid creating superflous names. Thus, the above example can also be written as: context Company { role Employee requires {void deposit(int)} { void getSalary(int salary) { deposit(salary);} ... } If a method specified in the required interface is "public", it can be called from outside, designating the role instance with appropriate qualification. Especially, it can be called in methods of other roles in the same context or in context methods. 2.4.3 Interface Method Overriding A method specified in the required interface can possibly be overridden by a role method with the same signature and the same return type. For example, context BankAccount { static role Account { int balance; void inc(int i) { balance += i;} } static role Customer requires {void deposit(int)} { void deposit(int i) { Account.inc(i); ... } ... } } 2.4.4 Interface Method Call with "Super" Qualifier In the overriding method or other methods in the same role, the original, un-overridden interface method can be called using the qualifier "super". For example, context BankAccount { static role Account { ... } static role Customer requires {void deposit(int)} { void deposit(int i) { super.deposit(i); Account.inc(i); ... } ... } However, "super" call cannot be made outside of the role that declares the corresponding required interface. 2.5 Inheritance In the role declaration, no inheritance of classes or interfaces is allowed. Likewise, a role definition cannot be inherited to other classes or roles. 2.6 Role Body RoleBody: {RoleBodyDeclarations_opt} RoleBodyDeclarations: RoleBodyDeclaration RoleBodyDeclarations RoleBodyDeclaration RoleBodyDeclaration: RoleMemberDeclaration InstanceInitializer ConstructorDeclaration RoleMemberDeclaration: FieldDeclaration MethodDeclaration RoleBody is like ContextBody except that RoleMemberDeclaration cannot be nested. 3. Object Binding/Unbinding to Role 3.1 Methods for Binding and Unbinding To bind and unbind an object to a role, a set of methods are predefined in each role: bind, newBind and unbind. 3.1.1 Methods for Binding public void bind(Object o) This method is applied to a "static" role or a role instance. When the role is "static", object "o" is bound to the singleton role instance. If the role is already bound to another object, that object is unbound from the role and "o" is bound instead. When the role is not "static", object "o" is bound to the role instance. If the role instance is already bound to another object, that object is unbound from the role instance and "o" is bound instead. For example, context Company { static role Employer { ... } role Employee { ... } } Person sasaki = new Person(); Person tanaka = new Person(); Person suzuki = new Person(); Company todai = new Company(); todai.Empoyer.bind(sasaki); /* "sasaki" is bound to the singleton instance of "Employer" */ Company.Employee clerk = new todai.Employee(); clerk.bind(tanaka); clerk.bind(suzuki); // "suzuki" is bound to "clerk" in place of "tanaka" public newBind(Object o) This method can be applied only to a "non-static" role group. Its function is to create a new instance of the role and binds object "o" to it. For example, context Company { role Employee { ... }} Person taro = new Person(); Company bank = new Company(); bank.Employee.newBind(taro); This is actually a shorthand for the two step procedure: (new bank.Employee).bind(o); The return value is a reference to the role instance created. 3.1.2 Method for Unbinding public unbind(); This method can be applied to a role instance or a static role. When the role is bound to an object, its binding is dissolved and the reference to the role instance is returned. When the role is not bound to an object, its effect is no operation. 3.2 Method Replacement in Binding Invocation of the methods, bind and newBind, is optionally accompanied by a "replacing clause". bind(Object) ReplacingClause_opt newBind(Object) ReplacingClause_opt ReplacingClause: replacing ReplacingPairs ReplacingPairs: ReplacingPair ReplacingPair , ReplacingPairs ReplacingPair: InterfaceMethodDeclarator with ObjectMethodDeclarator InterfaceMethodDeclarator: MethodDeclarator ObjectMethodDeclarator: MethodDeclarator MethodDeclarator: Identifier (FormalParameterList_opt) The first MethodDeclarator of the ReplacingPair must be a member of the interface required by the role to be bound. The second MethodDeclarator after the keyword "with" must be a member of the class that the binding object belongs to. The formal parameter number and types and the return type of the both methods must be identical but the method name can be different. If the binding object has a method with the same signature as a member method of the required interface, replacing pair description for them can be ommitted and the former is replaced by the latter automatically. In this case, if the return type of the both methods is not identical, a compile-error occurs. In any case, for each member of the required interface, it must be either explicitly replaced by a method of the binding object or implicitly replaced by a method with the same signature. For example, suppose a context is declared as follows: Interface IF { A f(B); C g(D); } Context C { static role R requires IF { ... }} There can be several cases. Case 1) class X implements IF { ... } X x = new X(); C c = new C(); c.R.bind(x); Here, the replacing phrase is unnecessary. Case 2) class X { A f(B); C g(D); ... } X x = new X(); C c = new C(); c.R.bind(x); Class X does not explicitly implements interface IF but the methods with the same signature and the same return type are defined and thus the replacing phrase can be omitted. Case 3) class X { A f(B); C h(D); ... } X x = new X(); C c = new C(); c.R.bind(x) replacing g(D) with h(D); The methods g and h have the same formal parameter types and return type; thus h can replace g. In this case, as f has the same method name, as well as the signature and the return type, the replacing phrase for them is omitted but it can be given explicitly. Thus, c.R.bind(x) replacing f(B) with f(B), g(D) with h(D); is a valid expression. 3.3 Multiple Binding An object can be bound to multiple roles of different context instances. An object can be bound even to multiple roles of the same context instance. However, an object cannot be bound to the same role of the same context instance, even when the role instances to be bound are different. 3.4 Semantics of Binding 3.4.1 Access to Role Methods When an object is bound to a role, it acquires an access to the role instance and thus can use the role methods. BindingRoleReference: ObjectReference . ( ContextReference . RoleName ) When an object is bound to a role, its role instance can be uniquely determined by the context instance and the role name (see section 3.4). Through this role instance reference, the object has access the role methods. For example, context Company { static role Employer { void pay(Employee e, int salary) { e.getPaid(salary); } } role Employee { int balance; void getPaid(int money) { balance += money; } } } Person sasaki = new Person(); Person tanaka = new Person(); Company todai = new Company(); todai.Empoyer.bind(sasaki); todai.Employee.newBind(tanaka); sasaki.(todai.Employer).pay(tanaka.(todai.Employee),100); Here, one may want to write: sasaki.pay(tanaka,100); but it is not allowed for the two reasons. 1) Since an object can be bound to multiple role instances, the above expression can be ambiguous. 2) By explicitly indicating the bound role, static type checking is possible. This mechanism can be regarded as a kind of delegation. Actually, the above statement can be written without referring the binding objects as follows. Company.Employee r_tanaka = todai.Employee.newBind(tanaka); todai.Employer.pay(r_tanaka,100); But for programming in Epsilon/J, it is recommended to think in terms of binding objects rather than in terms of role instances. 3.4.2 Method Replacement When the required interface methods are replaced by binding object methods, there are three cases to consider. Case 1: Method Import If an interface method is only used in role methods and not overridden, the replacing method is imported to the role from the object. When an interface method is called in a role method, the replacing method of the binding object is called instead with the same argument list supplied. Using the example in section 2.4.2, interface Deposit { void deposit(int);} context Company { role Employee requires Deposit { void getSalary(int salary) { deposit(salary);} ... } class Person { int balance; void save(int money) { balance += money;} } Person Tanaka = new Person(); Company todai = new Company(); todai.Employee.newBind(tanaka) replacing deposit(int) with save(int); Then, when "getSalary" method of the role instance bound to "tanaka" is invoked, the method "save" of "tanaka" will be called in its body in place of "deposit". Case 2: Method Export If an interface method is overridden in the role, the overriding method is exported from the role to the object. It means that after the binding whenever the replacing method of the object is invoked, the overriding method of the role is called instead with the same argument list supplied. Using the example in section 2.4.3, context BankAccount { static role Account { int balance; void inc(int i) { balance += i;} } static role Customer requires {void deposit(int)} { void deposit(int i) { Account.inc(i); ... } ... } } class Person { int balance; void save(int money) { balance += money;} } Person Tanaka = new Person(); BankAccount asahi = new BankAccount(); asahi.Customer.bind(tanaka) replacing deposit(int) with save(int); Then, when "save" of "tanaka" is invoked, "deposit" of the corresponding role instance is called instead. Case 3: Method Import & Export If an interface method is overridden and also the replacing method is called by "super" qualifier, the interface method is both imported and exported by the role. Using the example in 2.4.4, context BankAccount { static role Account { ... } static role Customer requires {void deposit(int)} { void deposit(int i) { super.deposit(i); Account.inc(i); ... } ... } Person Tanaka = new Person(); BankAccount asahi = new BankAccount(); asahi.Customer.bind(tanaka) replacing deposit(int) with save(int); Then, when "save" of "tanaka" is invoked, "deposit" of the corresponding role instance is called instead but as its first execution step, the original "save" of "tanaka" is called in place of "super.deposit". 3.4.3 Replacement of Multiple Role Methods by a Single Object Method As an object may bind to multiple role instances, the same object method may replace multiple role methods. We divide the situation into three cases. As binding and unbiding are dynamic operations, the cases may change dynamically. Case 1: replaced methods are all imported (not overridden) This is the simplest case. Each call of the role interface method will actually call the replacing object method. Case 2: replaced methods are all exported (overridden) When the replacing object method is called, all the overriding methods are called. The order of invocation is compiler dependent. The order of binding is one of the natural orders but it is not stipulated, allowing the possibility of concurrent invocation. When the method has a return value, the one from the last invocation will be returned, which actually means which one will be returned is not determined. Note that when one of the overriding methods (role methods) is called, the effect is the same, i.e. all the other overriding methods that share the same replacing method are called, because the effect of overriding is that the invocation of the overriding method is equivalent to the invocation of the replacing object method. Case 3: some replaced methods are imported, others are exported Call to the replacing object method that is overridden (and equivalently, call to the overriding role method) will result in the same behavior as stipulated in Case 2. Call to an interface method in the role with "super" qualifier will always result in calling the original replacing method of the binding object however it is overridden. When a call to an interface method is not qualified with "super", it calls the current overridden object method, the effect of which is the same as Case 2, i.e. when more than one role methods are overriding it, all of them will be called. This case may look complicated but the principle is very simple. The binding/unbinding mechnism is dynamic in nature and the current status of binding is always respected, except the explicit call of the original method with "super". 4. Other Predefined Methods of Role Besides binding and unbinding methods, some methods handling the current set of role instances are predefined. Object boundObject(); Applied to a role instance or a static role and returns the object it is bound. If no object is bound, "nil" will be returned. Iterator iterate(); Applied to a role group, returns an Iterator that iterates over the current role instances. The order of iteration is compiler dependent. A natural order of role instance creation order is suggested but not stipulated. In general, any role method can be applied to its role group as opposed to a role instance. For example, Context C { role R { void f(A a) { ... } Context c = new C(); c.R.newbind(x); c.R.newbind(y); c.R.newbind(z); /* "x", "y", "z" are assumed to have been assigned appropriate object references. */ c.R.f(a); /* "a" is assumed to have an appropriate value of type "A"; then x.(c.R).f(a), y.(c.R).f(a), and z.(c.R).f(a) are called (may not necessarily be in that order). The above call to f(a) is quivalent to for (Iterator i=c.R.iterate();i.hasNext();) { i.next().f(a); } When the method has a return value, the last one will be returned.