4. Mapping Ada to Java

To allow an Ada package to be used from Java, ada2java generates one or more Java classes (source files that will need to be compiled to bytecodes by a Java compiler) based on the content of the visible part of the Ada package spec. This section explains and illustrates the mapping for each of the various kinds of entities declared in a package that can be used from Java.

In brief:

  • Although there are some exceptions to this rule, in general a type and certain of its associated subprograms declared in an Ada package are mapped to a Java class with methods corresponding to the Ada subprograms. Such entities are said to be attached to the resulting class.

  • Other entities declared in the Ada package map to static members defined in a ‘default class’ generated by ada2java. In particular, variables and constants in the Ada package map to private static fields in the default class and are accessed through ‘’getter’’ (and ‘setter’ for variables) methods. Such entities are said to be unattached.

If the default class is generated, its name is that of the original Ada package (with the same casing as the identifier in the package declaration) suffixed with _Package.

In the examples, only the portions of the Java classes needed by users of the classes are shown.

4.1. Types

Types used in the Ada package map to Java types in the generated class(es). This section explains the correspondence. As a general rule, note that while most forms of type declarations have a correspondence in Java, subtype declarations are ignored, as there is no equivalent to subtypes in Java. However, subtype constraints imposed on Ada entities, such as variables or formal parameters, must be respected when referenced from Java, and can result in exceptions when constraints are violated.

4.1.1. Scalar Types

The following table shows how Ada scalar types are mapped to Java primitive types:

Ada type Java type
Integer type <= 32 bits int
Integer type > 32 bits long
Boolean boolean
Character char
Other enumeration type int
Fixed-point type double
Floating-point type double

Constraint checks generated in the Ada glue code detect errors that may result from the range mismatches between Ada and Java. For example, since a 16-bit Ada integer will be mapped to 32-bit int in Java, the Java code might attempt to pass an out-of-range value to Ada. This will raise a Constraint_Error exception in Ada, which will be propagated back to Java as an AdaException exception.

For an enumeration type, a Java final class is created, with the same name as the enumeration type. This class defines the possible values for the enumeration.

Example:

package Pckg is
   type Enum is (A, B, C);
end Pckg;

will give:

package Pckg;

public final class Enum {
   public static final int A = 0;
   public static final int B = 1;
   public static final int C = 2;
}

Representation clauses for enumeration types are not currently supported.

A discussion of subprogram formal parameters of scalar types may be found in Subprogram parameters.

4.1.2. Arrays

Mapping Ada arrays to Java arrays would be very expensive, since it would imply a copy of the whole array each time a parameter has to be passed. Thus for efficiency an Ada array type is mapped to a dedicated ‘proxy’ class with methods that serve as accessors to attributes and components. For example:

package Ex1 is
   type T1 is array(Integer range <>) of Float;
end Ex1;

will yield the following class:

public final class T1 extends com.adacore.ajis.internal.ada.AdaProxy {
   ...
   public T1 (int First_1, int Last_1){...}

   final public double Get_Element_At (int Index_1){...}

   final public void Set_Element_At (int Index_1, double Value){...}

   final public int First (){...}

   final public int Last (){...}

   final public int Length (){...}
}

A subprogram that takes a parameter of the Ada array type is mapped to a method taking a parameter of the corresponding Java ‘proxy’ class; note that this method is located in the default class, and not in the proxy class.

4.1.3. Strings

Directly passing String data between Ada and Java would require expensive copying, and thus an alternative approach is used. The Ada type String is mapped to the Java class AdaString, which encapsulates the accesses.

More specifically, an Ada parameter of type String of any mode, and an Ada access String parameter, are both mapped to a Java parameter of type AdaString.

For efficiency, an AdaString object caches both its Ada and Java string values after they have been computed. As an example, if the Ada spec is:

package Pckg is
   procedure P (V : String);
end Pckg;

then the generated Java will be:

public final class Pckg_Package {
   public static void P (V : AdaString) {...}
}

If we now write:

AdaString str = new AdaString ("A string from Java");
Pckg_Package.P (str);
Pckg_Package.P (str);

Only the first call will require the expensive string translation from Java to Ada. The second invocation will directly use the cached value.

Please note that Java strings are UTF16-encoded, whereas the corresponding Ada strings will be UTF8-encoded. This may have significant impact when computing character offset on Java strings.

4.1.4. Simple Record Types

Simple (that is, not tagged) record types are mapped to Java final classes. Components are accessed through a set of generated accessors (‘getter’ / ‘setter’ methods). As a current limitation, ada2java does not yet support accessing discriminant components.

Example:

package Pckg is
   type R is
      record
         F1 : Integer;
         F2 : Float;
      end record;
end Pckg;

will give:

package Pckg;
public final class R {
   public R () {...}

   public final int F1 () {...}
   public final void F1 (int Value) {...}

   public final double F2 () {...}
   public final void F2 (double Value) {.../}
}

A component that has an access-to-record type is treated as though it were of the record type itself. For example:

package Pckg is
   type R is
      record
         F1 : Integer;
         F2 : Float;
      end record;
   type S is
      record
         G1 : R;
         G2 : access R;
      end record;
end Pckg;

will result in both a class R as above, and the following class S:

package Pckg;
public final class S {
   public S () {...}

   public R G1 () {...}
   public void G1 (R Value) {...}

   public R G2 () {...}
   public void G2 (R Value) {...}
}

Only one level of indirection is implemented; ada2java does not support access to access-to-record.

A private (untagged) type is treated like a record type, except that it does not have any component-accessing methods. (A later release of ada2java will generate methods for accessing discriminants if the type has any.)

4.1.5. Tagged Types

A tagged type is mapped to a Java class of the same name. If the Ada type is abstract, then the Java type will be abstract as well.

4.1.5.1. General principles

A primitive (i.e., dispatching) subprogram of a tagged type is mapped to a corresponding Java instance method. A current restriction is that the first parameter of the Ada subprogram must be a controlling parameter; otherwise the subprogram is mapped to a method in the default class. (Thus a function that delivers a value of the tagged type, but has no controlling parameter, is mapped to a method in the default class, and not to a method in the class corresponding to the Ada type.) The first Ada parameter is mapped to the Java method’s implicit this parameter.

A subprogram with a class-wide parameter is mapped to a method of the tagged type’s Java class whose corresponding parameter has the Java class type. However, as this is not properly a dispatching primitive of the Ada type, it is declared as a final method.

As an example:

package Ex1 is
   type T is tagged null record;
   procedure P1 (X : in out T; F : Float);
   procedure P2 (X : T'Class);
   procedure Q1 (I : Integer; X : T);
   procedure Q2 (I : Integer; X : T'Class);
   function F return T;
end Ex1;

is mapped to:

public final class Ex1_Package {
   ...
   static public void Q1 (int I, Ex1.T X){...}

   static public void Q2 (int I, Ex1.T X){...}

   static public T F (){...}
} // Ex1_Package

public class T extends com.adacore.ajis.internal.ada.AdaProxy {
   ...
   public void P1 (double F){...}

   final public void P2 (){...}
} // T

4.1.5.2. Ada type hierarchies

Hierarchies of Ada types are preserved in the generated Java classes. Therefore, the following structure:

type R is tagged record;

type R_Child is new R with null record;

will result in:

public class R {...}

public class R_Child extends R {...}

Consistency of Java types is guaranteed at run time. For example, the following function:

package Pckg is
   function F return R'Class;
end Pckg;

will result in:

public final class Pckg_Package {
   public R F () {...}
}

However, if the actual type of the returned object is R_Child, then the value returned by the Java function will be of the Java type corresponding to R_Child.

4.1.5.3. Java class hierarchies

It is possible to extend a Java class that was generated by ada2java from an Ada tagged type.

For example:

package Rec_Pckg
   type Rec is tagged null record;
   procedure P (R : Rec);
end Rec_Pckg;

results in a Java class Rec with an instance method P:

class Rec extends com.adacore.ajis.internal.ada.AdaProxy {
   ...
   public void P(){...}
}

You can then write:

class Rec_Child extends Rec {
     public void P () {
        System.out.println ("Hello from Java");
     }
}
...
Rec ref = new Rec_Child();
ref.P();  // Displays "Hello from Java"

4.2. Global Variables and Constants

A package containing global variables (that is, variables declared in the package spec’s visible part) is mapped to a default class containing ‘getter’ and ‘setter’ methods for accessing and updating the variables. Global constants are treated analogously, but they have only a ‘getter’ method. Variables of limited types also only have a ‘getter’ method. Note that Ada named numbers, which are really just values intended for use in static compile-time computations, are not mapped to Java.

For example:

package Globals is
   V : Integer;
   C : constant Integer := 100;
   N : constant := 3.14159;
end Globals;

will result in the default class:

public class Globals_Package {

   static public int V (){...}

   static public void V (int Value){...}

   static public int C (){...}
}

4.3. Subprograms

Ada procedures and functions are mapped to Java methods. Nondispatching subprograms are marked as final. Dispatching subprograms are discussed in Tagged Types.

4.3.1. Method placement

In general, nondispatching subprograms are mapped to methods defined in the default class.

For example, if the input package spec is:

package Pkg is
  function F return Integer;
end Pkg;

then ada2java will generate the following default class:

public class Pkg_Package{
   ...
   public static int F(){...}
}

However, there are cases where the subprogram can be attached to the class of its first parameter. Attachment can be enabled / disabled depending on user requirement. In this case, the explicit initial Ada parameter is mapped to the implicit this parameter in Java. See Managing Attachment to Java Proxies for further details.

4.3.2. Subprogram parameters

The following rules and restrictions apply to the types of subprogram formal parameters:

  • Scalar types

    • Access-to-scalar types are not supported.

    • A scalar type with mode in is mapped to the corresponding Java type. For example:

      procedure P (V : Integer);
      

      will result in:

      public void P (int V) {...}
      
    • A formal scalar with mode out or in out will be mapped to a corresponding ‘wrapper’ class: BooleanRef, CharacterRef, DoubleRef, IntegerRef, and LongRef respectively encapsulating the primitive type boolean, character, double, int and long).

      Each of these classes defines setValue and getValue methods for accessing the encapsulated value.

      The Java application needs to construct an object of the relevant wrapper class and pass it to the method that corresponds to the Ada subprogram. After the return from the method, the Java application can invoke the getValue method to retrieve the new value of the actual parameter.

  • Record and private types

    • An Ada in, in out, or out formal parameter of a record or private type (either tagged or untagged), is mapped to a Java formal parameter of the class corresponding to the Ada type. Similarly, an Ada formal parameter of an access-to-record-type or access-to-private-type (either anonymous or named) is mapped to a Java formal parameter of the class corresponding to the Ada type.

      Example:

      package Example is
         type R is null record;
         type Access_R is access all R;
         procedure P(V1 : R;
                     V2 : out R;
                     V3 : in out R;
                     V4 : access R;
                     V5 : Access_R);
      end Example;
      

      The resulting Java class is:

      public final class Example_Pckg {
         ...
         public void P (R V1, R V2, R V3, R V4, R V5){...}
      }
      
    • An Ada out or in out parameter of an access-to-record (or access-to-private) type is mapped to a nested class. For example:

      package Example is
         type R is null record;
         type Access_R is access all R;
         procedure P(V : out Access_R);
      end Example;
      

      will generate the default class and a class for R

      public class Example_Package {
         ...
         static public void P (R.Ref V){...}
      }
      
      public class R extends com.adacore.ajis.internal.ada.AdaProxy {
         ...
         public static class Ref implements com.adacore.ajis.IProxyRef {
            public void setValue (Object r) {...}
            public Object getValue () {...}
         }
      }
      

      The Java application needs to construct an object of the class R.Ref and pass it to P. On return, the getValue method may be called to retrieve the value in the out parameter returned by the Ada procedure.

    • Further indirection, such as an access-to-access type for a formal parameter, is not supported.

4.3.3. Overloaded operators

Java doesn’t allow operators overloading. When operators are overloaded in Ada, the corresponding Java name is set by the binding generator to OP_<operator_name>. For example:

type Complex is record ...

function "+" (Left, Right : T) return T;

will generate on the Java side:

public class Complex {

   public Complex OP_PLUS (Complex Right) {
   ...
   }

}

Here’s a list of the equivalence between Ada operators and Java names:

Ada operator Java name
= OP_EQUAL
> OP_GT
< OP_LT
>= OP_GE
<= OP_LE
or OP_OR
and OP_AND
xor OP_XOR
+ OP_PLUS
- OP_MINUS
/ OP_DIV
* OP_MUL
** OP_EXP

4.4. Subprogram Access Types

Accesses to subprograms - sometimes referred to as callbacks - can’t be directly bound to Java. It is not possible to give a reference to a Java function in a type-safe fashion. ada2java generates an abstract class with an abstract member of the correct profile for each access type to be bound, the implementation of its abstract primitive being the implementation of the subprogram access. For example:

type P_Acc is access all procedure (V : Integer);

procedure Call_P_Acc (Proc : P_Acc);
pragma Annotate (AJIS, Assume_Escaped, False, Call_P_Acc, "Proc");

will be bound into:

abstract public class P_Acc {
   abstract public P_Acc_Proc (int V);
}

void Call_P_Acc (P_Acc Proc);

and can be used in, for example, the following scenario:

Proc p = new Proc () {

   public P_Acc_Proc (int V) {
      System.out.println ("CALLED WITH " + V);
   }

};

Pckg_Package.Call_P_Acc (p);

Note the use of the pragma Annotate on the Call_P_Acc method. The Java implementations of bound subprogram access types are not actually accesses to subprograms, but instances of Java objects. It’s not possible to store such an object on the Ada side afterwards, since the complete information can’t be kept in the access type. Therefore, the programmer must ensure that no escape of the value is done, and take responsiblity for that by declaring the parameter as being not escaped. Further details on escapement can be found in Restrictions on Proxy-Owned Objects Passed to Subprograms.

4.5. Exceptions

Exceptions are bound into classes derived from com.adacore.ajis.NativeException It is then possible to throw or handle them directly in Java code.

Example:

package Example is
   An_Exception : exception;
   procedure Raise_An_Exception;
end Example;

package body Example is
   procedure Raise_An_Exception is
   begin
      raise An_Exception;
   end Raise_An_Exception;
end Example;

The resulting Java class is:

public final class An_Exception extends com.adacore.ajis.NativeException {
   ...
}

And can be used in e.g.:

try {
   Example_Package.Raise_An_Exception ();
} catch (An_Exception e) {
  // process the exception
}

4.6. Renamings

Renamings of objects and subprograms are supported by ada2java. Object renamings are mapped in the same way as global objects, by means of ‘setter’ and ‘getter’ methods in the default class for the containing package. A subprogram renaming is represented by a method with the name of the renaming that invokes the renamed subprogram, declared in the appropriate class. In other words, the same rules that apply to other subprograms apply to subprogram renamings.

4.7. Generics

Generic packages and subprogram can’t be directly bound to Java. However, packages and subprograms instances and bound like regular packages and subprograms.

4.8. Predefined Environment

In order to access descendants of Ada or GNAT from Java, you need to manually invoke ada2java on the Ada source files from the GNAT installation directories, to generate the corresponding Java binding classes. This step will be automated in a future release of GNAT-AJIS.

4.9. Current Limitations

The following features are not supported:

  • Discriminants. Discriminants are not accessible from the Java class generated for a discriminated type.
  • Anonymous arrays. Objects with an anonymous array type are not supported, but array type declarations which declare a constrained first subtype are supported.
  • Interfaces. No mapping is currently provided from Ada interface types to Java interfaces.
  • Tasking features. Tasks and protected objects/types are ignored.