Personalities/J Compiler
Project Description
ATTENTION: For the latest information about Personalities and
the Personalities/J compiler definition, please consult Chapter 4
of the
Personalities Thesis
For a complete example of both static and dynamic personalities please
download: PJ Example
Introduction
Personalities/J is an extension to the Java language to allow for a
more flexible way of designing software systems based on roles. Java is
extended to support a new entity, a personality, which resembles
an interface but contains the implementation of certain functions and imposes
a number of semantic constraints both on its usage and its implementation.
For much more information about personalities, please see
this page.
The Personalities/J Compiler
The purpose of the compiler is to take a set of .pj
files and turn them into .wvr (Weaver) files. The Weaver module is part of the
Demeter/Java system and is used to easily implement the code-generation phase of
the compiler. Alternatively, a student might elect not to use the weaver in
which case the Personalities/J compiler will output standard Java code. For more
information about the weaver tool see
http://www.ccs.neu.edu/home/jayantha/usermanual/node6.html.
Figure 1 shows the two potential architectures of the Personalities/J
compiler.
Figure 1: Two architectures of the Personalities/J
compiler (pjc)
Implementation Details
To implement the compiler, we suggest using Demeter/Java’s extensive
parsing and traversal facilities. To illustrate in more detail what the
compiler is supposed to do, we will reproduce parts of the paper mentioned
above.
Details about Personalities
Defining a personality is similar to defining a class, with a few added
keywords. For example, the Flier
personality
can be defined as follows (new keywords are underlined):
// Flier.pj
personality Flier {
// upstream
interface. Must implement here.
public
void Fly(int
miles, int altitude) {
Takeoff();
for (int a=0; a < altitude; a++) Ascend();
while(miles--) Flap();
for(a = altitude; a > 0; a--) Descend();
Land();
}
// downstream
interface. Don’t impl here.
di
void Takeoff();
di
void Ascend();
di
void Flap();
di
void Descend();
di
void Land();
private some_other_function()
{...}
}
A personality definition consists of three basic elements:
-
The upstream interface, made up of all the member
functions that clients of this personality can access (one or more). These
encapsulate what we have been calling "popular" behavior. It is here where
the personality adds value to the design, since it provides a single and
unique implementation of such behavior.
-
The downstream interface, composed of only signatures
for functions prepended by the di
keyword.
These are the functions that personifying classes must implement. Clients
of the personalities cannot access these methods.
-
Any other functions that the personality might need to implement
the upstream interface. These functions are not visible to either clients
or personifying classes.
When a class decides to personify a given personality, it needs to declare
its intent (via the personifies
clause),
as well as provide the methods specified in the downstream interface. For
example, a (trivial) definition of the Mosquito
class could be:
public class Mosquito extends
Insect
personifies Flier
{
int height
= 0;
public Mosquito()
{}
private boolean
stingingSomebody() {...}
private void
jumpInTheAirAndStartFlapping() {}
// downstream
interface implementation
public void
Takeoff() {
while ( stingingSomebody() ) {
// wait for some time
}
jumpInTheAirAndStartFlapping();
}
public void
Ascend() {...}
public void
Flap() {...}
public void
Descend() {...}
public void
Land() {...}
}
Continuing with the example, the definition of the Walker personality follows:
//Walker.pj
personality Walker
{
public void
Walk(int distance) {
Prepare();
while( distance-- ) {
for(int f=0; f < NumberOfFeet(); f++)
MoveFoot(f);
Stabilize();
}
AtEase();
}
di void Prepare();
// downstream
di int NumberOfFeet();
// interface
di void MoveFoot(int);
di void Stabilize();
di void AtEase();
}
The definition of the Locust class follows:
// Locust.pj
public class Locust extends
Insect
personifies Walker, Flier
{
// ... details
about Locust class ...
// as required
by Walker.pj
void Prepare()
{ ... }
int NumberOfFeet()
{ ... }
void MoveFoot(int
no) { ... }
void Stabilize()
{ ... }
void AtEase()
{ ... }
// as required
by Flier.pj
void Takeoff()
{ ... }
void Ascend()
{ ... }
void Flap()
{ ... }
void Descend()
{ ... }
void Land()
{ ... }
}
The Personalities/J compiler will generate:
-
the interfaces Walker.java
and Flier.java
which lay down the signatures for both the upstream and downstream interfaces
of the Walker
and Flier
personalities, respectively
-
the Walker$Ego.java
and Flier$Ego.java
classes which contain the implementations of the upstream behavior of
Walker and
Flier personalities, respectively, and
-
the class Locust.java for the concrete
animal class that personifies both a Flier
and a Walker.
Sample generated code is shown below:
// Walker.java
interface Walker {
void Walk(int
distance);
void Prepare();
int NumberOfFeet();
void MoveFoot(int
feetno);
void Stabilize();
void AtEase();
}
// Flier.java
interface Flier {
void Fly(int
miles, int altitude);
void Takeoff();
void Ascend();
void Flap();
void Descend();
void Land();
}
// Locust.java
public class Locust extends
Insect
implements Walker, Flier
{
// ... details
about Locust class ...
// as required
by Walker.pj
void Prepare()
{ ... }
int NumberOfFeet()
{ ... }
void MoveFoot(int
no) { ... }
void Stabilize()
{ ... }
void AtEase()
{ ... }
// as required
by Flier.pj
void Takeoff()
{ ... }
void Ascend()
{ ... }
void Flap()
{ ... }
void Descend()
{ ... }
void Land()
{ ... }
// compiler-generated
code
Walker$Ego
$walker = new Walker$Ego();
public void
Walk(int distance)
{ $walker.Walk(this,
distance); }
Flier$Ego
$flier = new Flier$Ego();
public void
Fly(int miles, int altitude)
{ $flier.Fly(this,
miles, altitude); }
}
// Walker$Ego.java
public class Walker$Ego
{
public void
Walk(Walker host, int distance) {
host.Prepare();
while( (distance--) > 0 ) {
for (int f=0; f<host.NumberOfFeet(); f++)
host.MoveFoot(f);
host.Stabilize();
}
host.AtEase();
}
}
// Flier$Ego.java
class Flier$Ego
{
public void
Fly(Flier host, int miles,
int altitude) {
host.Takeoff();
for (int a = 0; a < altitude; a++)
host.Ascend();
while((miles--) > 0) host.Flap();
for(int a = altitude; a > 0; a--)
host.Descend();
host.Land();
}
}
A Hand-Compiled Example
To better understand the input passed to the compiler, as well as its
ultimate output, we have provided a hand-compiled example. You can test
your compiler against these files to make sure it works. Download the
example.zip file here.
Work in Progress
Research in Personalities is evolving. Some of the things we are thinking
about include:
-
how to implement inheritance among personalities
-
how to effectively compose ("plug") personalities
-
how to effectively implement dynamic personalities
-
how to avoid losing static type checking when moving to fully dynamic personalities.
Thus, the Personalities/J compiler definition might change in the future.
Suggestions, ideas, and criticism are welcome and expected.
How to Get Help
Please email me at:
lblando@alum.mit.edu