Inspirel banner

Programming Distributed Systems with YAMI4

10.4.3 Java

The Java language mapping was designed in a way that should not be surprising to users of similar code-generation approaches like CORBA's IDL.

YDL packages are mapped to Java packages and following common conventions, source files for each generated class are written in the respective directory. Package imports as well as package parent-child relationships are mapped to the import directive for respective classes.

Types from YDL are mapped to classes in Java.

Type fields are mapped to member fields.

Field types are mapped to standard Java types in the following way:

Optional fields are mapped to pairs of member fields, where the normally generated field is accompanied by the boolean field named FieldValid that allows to express whether the given field is valid or not.

Each generated structure type has two member functions, write and read, which can be used to automate translation of the given structure type to and from com.inspirel.yami.Parameters.

For example, the following type description in YDL (taken from the calculator example):

   type Results is
      Sum : Integer;
      Difference : Integer;
      Product : Integer;
      Ratio : optional Integer;
   end Results;

is mapped to the following Java definition:

struct results
{
    results();

    void write(yami::parameters & params) const;
    void read(const yami::parameters & params);

    int sum;
    int difference;
    int product;
    bool ratio_valid;
    int ratio;
};

and the following code in respective implementation file:

public class Results {

    public int sum;
    public int difference;
    public int product;
    public boolean ratioValid;
    public int ratio;

    public Results() {
        ratioValid = false;
    }

    public void write(Parameters params) {
        params.setInteger("sum", sum);
        params.setInteger("difference", difference);
        params.setInteger("product", product);
        if (ratioValid) {
            params.setInteger("ratio", ratio);
        }
    }

    public void read(Parameters params) {
        sum = params.getInteger("sum");
        difference = params.getInteger("difference");
        product = params.getInteger("product");
        Parameters.Entry e;
        e = params.find("ratio");
        ratioValid = e != null;
        if (ratioValid) {
            ratio = params.getInteger("ratio");
        }
    }
}

As can be seen, optional fields allow to work with parameters entries that might or might not exist at all. Classes that are generated for user-defined types are used on both client- and server-side.

It should be noted that these functions do not clear the target object before performing the translation - this means that they are working in the incremental way, which can provide potential flexibility.

Interfaces, on the client-side, are mapped to classes with the same name that encapsulate the connection details of the remote object - the agent object in use, the server location, object name and requested operation timeout. The generated class has a constructor that allows easy initializaton and a set of functions, one for each message declared in the interface that encapsulate complete interactions with remote object and take care of parameter translation as well.

For the calculator example, the Operations interface is mapped to the class type:

public class Operations {

    private Agent agent;
    private String serverLocation;
    private String objectName;
    private long timeout;

    public Operations(Agent agent, String serverLocation,
        String objectName, long timeout) {

        this.agent = agent;
        this.serverLocation = serverLocation;
        this.objectName = objectName;
        this.timeout = timeout;
    }

    public Operations(Agent agent,
        String serverLocation, String objectName) {

        this(agent, serverLocation, objectName, 0L);
    }

    public void calculate(Operands op, Results res)
        throws Exception {

        // ...
    }
}

As can be seen above, the user-defined types and their respective classes in generated code are used in the calculate signature in a natural way.

The run-time implementation of this operation involves the following sequence of actions:

This sequence of actions allows to provide the remote procedure call functionality.

As a counterpart of these definitions, the generated code contains also an abstract class with abstract functions for use on the server-side. This class is intended to be a base class for user-defined extensions that provide the actual code for all message operations.

The Operations interface is mapped to the following server-side abstract class in Java:

public abstract class OperationsServer
    implements IncomingMessageCallback {

    public OperationsServer() {
    }

    public abstract void calculate(Operands op, Results res)
        throws Exception;

    public void call(IncomingMessage msg) throws Exception {
        String msgName = msg.getMessageName();

        // ...
    }
}

The specializations of this type can be directly registered as YAMI4 objects thanks to the function call operator that redirects the incoming calls to virtual functions defined for each message in the given interface. In the calculator example the calculate virtual function above is supposed to be overriden by application code, where the actual implementation of this operation should be placed.

The extensions of this class can be directly registered as YAMI4 objects thanks to the IncomingMessageCallback interface that underlies the whole hierarchy and which, with the help of the call function, redirects the incoming calls to virtual functions defined for each message in the given interface.