Java Kernel - An Example

RUNES Layers Here we provide a brief tutorial on how to write components for the Java RUNES middleware through a sample application. Our goal is to write a simple component-based calculator to perform additions and multiplications, as illustrated in the figure. The operations are performed by connecting the component representing the Calculator to two components implementing addition and multiplication, respectively. The receptacle of Calculator is meant to be used by another application component willing to use its services.

Let us start from interfaces. In this example, we just need two interfaces, one for each component we plan to implement. In particular, we need an IAdder interface with a single operation used to add two integers. Then, we need an IMultiplier interface with a single operation used to multiply two integers. Finally, an ICalculator interface is needed with two operations, one for adding and one for multiplying two integers. Clearly, the former will be mapped to the corresponding method in the IAdder interface, whereas the latter will be mapped to the corresponding method in IMultiplier. The actual code for the three interfaces is reported next. Note all of them extend the basic RUNES interface Interface.

public interface IAdder extends Interface {
   public int add(int x, int y);
}
public interface IMultiplier extends Interface {
   public int multiply(int x, int y);
}
public interface ICalculator extends Interface {
   public int add(int x, int y);
   public int multiply(int x, int y);
}

Note that splitting the calculator into multiple components, where each of them performs a particular operation, allows one to transparently and dynamically substitute the component providing a given operation without the rest of the system being aware of this. Indeed, the calculator component needs to be aware only of the IAdder and IMultiplier interfaces. In other words, the Calculator component depends only on the operation signatures in the IAdder and IMultiplier interfaces, not on the way these operation are realized. Therefore, the components behind these interfaces can be changed transparently, even at runtime.

After having defined an interface, we have to implement at least a component implementing it. The code of a simple component implementing the IAdder interface is shown next. Here, the addition is performed using the usual Java arithmetic operators. However, once an interface is defined, the developer can freely decide how to implement a given operation. Furthermore, notice that every time a RUNES component is implemented, it has to be written by extending the BaseComponent class in order to be recognized as a RUNES component by the rest of the framework. Apart from this, the developer writes the actual code for all the methods defined in the interface(s) the component is implementing. In addition, the construct() and destroy() methods have to be implemented to perform all the necessary operations at component creation and destruction time, respectively. As in the SimpleAdder component there is no setup or clean-up to perform, these two methods simply print a message on the standard output.

public class SimpleAdder extends BaseComponent implements IAdder {
   public void construct() throws ComponentException {
     System.out.println("Adder component instantiated");
   }
   public void destroy() throws ComponentException {
     System.out.println("Adder component destroyed");
   }
   public int add(int x, int y) {
     return x+y;
   }
}


An implementation for the Calculator component is reported in the following code fragment. In this case, the construct() method is responsible for creating the two receptacles the component needs. This is accomplished with the createSingleReceptacle(String interfaceName) method. Later, a reference to such a receptacle can be retrieved using the getSingleReceptacleTo(String interfaceName) method. Then, the receptacle can be used by means of the getSingleConnectedInterface() method, that returns a reference to the Interface connected to this receptacle. Notice that there are no guarantees that the receptacle is actually connected to a given interface. For instance, a receptacle might turn out to be disconnected when the connected Component have been destroyed. For this reason, every time a getSingleConnectedInterface() method is invoked, one has to explicitly manage the exceptions possibly raised. Moreover, let us point out that the getSingleConnectedInterface() method has to be invoked systematically every time we want to use a receptacle. Indeed, if one would simply invoke it at component creation time and store the reference to the interface internally, then it might be the case that the reference gets not updated in case the connected component is destroyed or changed.

public class Calculator extends BaseComponent implements ICalculator {
   public void construct() throws ComponentException {
     createSingleReceptacle("runes.sampleApp.IAdder");
     createSingleReceptacle("runes.sampleApp.IMultiplier");
     System.out.println("Calculator component instantiated");
   }
   public void destroy() throws ComponentException {
     System.out.println("Calculator component destroyed");
   }
   public int add(int x, int y) {
     SingleReceptacle r = getSingleReceptacleTo("runes.sampleApp.IAdder");
     try {
       return ((IAdder) r.getSingleConnectedInterface()).add(x, y);
     } catch(ReceptacleException e) {
       if(e.getKind()==ReceptacleException.NOT_CONNECTED)
         System.err.println("Adder Component not connected!");
       }
     return 0;
   }
   public int multiply(int x, int y) {
     SingleReceptacle r = getSingleReceptacleTo("runes.sampleApp.IMultiplier");
     try {
       return ((IMultiplier) r.getSingleConnectedInterface()).multiply(x, y);
     } catch(ReceptacleException e) {
       if(e.getKind()==ReceptacleException.NOT_CONNECTED)
         System.err.println("Multiplier Component not connected!");
       }
     return 0;
     }
}


Once we created the components, we wire them together to perform some useful computation. To this end, we need to implement a connector component for each of the interfaces we defined. A connector is a particular component meant to represent the connection between two standard components. It can either simply pass the operation to the connected component without further intervention or perform some more complex operation before passing the operation to the connected component, e.g. to monitor communication between the connected parties or to implement a distributed connection among components residing on different hosts. A simple connector for the IAdder interface is shown next. Clearly, this is the most simple connector one could implement, as it simply pass the operation to the component implementing the IAdder interface. Notice that, as the SimpleAdder component extends the BaseComponent class, the IAdderDefaultSingleConnector connector extends the BaseConnector class. In addition, it also implements the same interface of the component it is meant to connect.

public class IAdderDefaultSingleConnector extends BaseConnector
implements IAdder {
   public void construct() throws ComponentException {
     System.out.println("IAdderDefaultSingleConnector instantiated");
   }
   public void destroy() throws ComponentException {
   System.out.println("IAdderDefaultSingleConnector destroyed");
   }
   public int add(int x, int y) {
     return ((IAdder)getConnectedInterface()).add(x,y);
   }
}


Having implemented components and connectors, what remains to do is to wire everything together. To this end, the methods of the capsule have to be exploited. First of all, we need to load the necessary component types. For instance, let us consider the calculator component. Its component type can be loaded as follows:

ComponentType calculatorType = capsule.load(new StringPattern( "runes.sampleApp.Calculator"));

With its component type, we can now instantiate a component of type calculator as follows:

Component calculator = capsule.instantiate(calculatorType);

In the same way, we can load component types for the SimpleAdder and SimpleMultiplier components and then instantantiate two of them. Let us call them adder and multiplier.

We are now ready to connect components togheter. For instance, to connect the Calculator to a SimpleAdder, we first retrieve a reference to the interface of the SimpleAdder component and to the corresponding receptacle in Calculator. Once we obtained these references, we invoke the bind() method of the capsule and get a connector component representing the binding:

Interface adderIf = (Interface) capsule.getAttr(adder, "INTERFACE-runes.sampleApp.IAdder").getValue();
Receptacle calculatorAdderRecpt = (Receptacle) capsule.getAttr(calculator, "RECEPTACLE-runes.sampleApp.IAdder").getValue();
Connector calcToAdder = capsule.bind(adderIf, calculatorAdderRecpt);


In case we later want to destroy this binding, we destroy the connector component representing it as we would destroy any other component, i.e., by means of the destroy() method.