5. Advanced ada2java Topics

This chapter discusses a number of issues that ada2java users should be aware of.

5.1. Renaming bound record types

Bound record or tagged record java class name can be customized using the Rename AJIS pragma.

Example:

package Example is
  type Tagged_Record_Type is tagged null record;
  pragma Annotate (AJIS, Rename, Tagged_Record_Type, "TaggedRecordType");
end Example;

This will result in a TaggedRecordType java class.

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file, applying it to an Ada entity that is itself a subtype declaration.

Example:

with Example;
package Renamings is
  subtype Tagged_Record_Type is Example.Tagged_Record_Type;
  pragma Annotate (AJIS, Rename, Tagged_Record_Type, "TaggedRecordType");
end Renamings;

5.2. Dealing with Name Clashes

If Ada subprograms from the same package spec produce the same Java profile, the binding generator will detect the problem and generate only the first entity. Other entities of similar name will be ignored with a warning. To prevent this, you can use the Rename pragma to define the Java name corresponding to an Ada entity:

The binding generator may generate code containing ambiguous operand in conversion errors. See Dealing with ambiguous operand in conversion errors to solve these errors.

pragma Annotate (AJIS, Rename, <identifier>, <static_string_expression>);

The <identifier> argument denotes the Ada entity. The <static_string_expression> is the name that will be used for the corresponding Java entity generated by ada2java.

Example:

package Example is
  type I1 is new Integer;
  type I2 is new Integer;

  function F return I1;
  pragma Annotate (AJIS, Rename, F, "F_I1");

  function F return I2;
  pragma Annotate (AJIS, Rename, F, "F_I2");
end Example;

This will result in two Java functions:

int F_I1 ();
int F_I2 ();

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file, applying it to an Ada entity that is itself a renaming declaration.

This entity must be marked by another Annotate pragma:

pragma Annotate (AJIS, Annotation_Renaming, <identifier>).

where <identifier> denotes an entity defined by an Ada renaming declaration. The Annotation_Renaming pragma applies to all AJIS pragmas that are specified for <identifier>.

package Example is
  type I1 is new Integer;
  type I2 is new Integer;

  function F return I1;
  function F return I2;
end Example;

with Example;
package Renamings is
  function F return Example.I1 renames Example.F;
  pragma Annotate (AJIS, Annotation_Renaming, F);
  pragma Annotate (AJIS, Rename, F, "F_I1");

  function F return Example.I2 renames Example.F;
  pragma Annotate (AJIS, Annotation_Renaming, F);
  pragma Annotate (AJIS, Rename, F, "F_I2");
end Renamings;

Entities annotated with pragma Annotate(AJIS, Annotation_Renaming) will not be mapped to Java entities; they are assumed to be used only to define annotations.

5.3. Dealing with ambiguous operand in conversion errors

The Ada code created by the binding generator may contain “ambiguous operand in conversion” errors.

To solve them:

  • ask the binding generator to generate Ada code resolving the ambiguous expression (using Resolve_Ambiguous_Expression AJIS Annotate pragma)
  • remove the unused possible interpretation(s) from the generated code. (see Removing function/procedure from binding layer)
pragma Annotate (AJIS, Resolve_Ambiguous_Expression, <identifier>, True);

The <identifier> argument denotes the Ada entity to bind using extra code to resolve the “ambiguous operand in conversion” error.

Example:

with AJIS.Annotations; use AJIS.Annotations;

package Example is

  type T is tagged null record;

  function F return T;
  pragma Annotate (AJIS, Resolve_Ambiguous_Expression, F, True);

  function F return Integer;

end Example;

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file.

with AJIS.Annotations; use AJIS.Annotations;

with Example;

package Config is

  function F return Hello.T renames Hello.F;
  pragma Annotate (AJIS, Resolve_Ambiguous_Expression, F, True);

end Config;

5.4. Removing function/procedure from binding layer

Any unused function/procedure can be removed from binding using the Bind AJIS Annotate pragma.

pragma Annotate (AJIS, Bind, <identifier>, False);

The <identifier> argument denotes the Ada entity to remove from the binding layer.

Example:

with AJIS.Annotations; use AJIS.Annotations;

package Example is

  type T is tagged null record;

  function F return T;
  pragma Annotate (AJIS, Bind, F, False);

  function F return Integer;

end Example;

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file.

with AJIS.Annotations; use AJIS.Annotations;

with Example;

package Config is

  function F return Hello.T renames Hello.F;
  pragma Annotate (AJIS, Bind, F, False);

end Config;

5.5. Allowing Java object creation even if the primitive cannot be bounded

If a primitive of a tagged record cannot be bounded, use “Allow_Java_Creation” or “Allow_Java_Creation_And_Child_Types” qualifier of the Bind AJIS Annotate

pragma to avoid having java object creation removed because this primitive cannot be bounded.
pragma Annotate (AJIS, Bind, <identifier>, "Allow_Java_Creation");
pragma Annotate (AJIS, Bind, <identifier>,
                 "Allow_Java_Creation_And_Child_Types");

The <identifier> argument denotes the Ada entity to take into account as unbounded primitive.

Example:

with AJIS.Annotations; use AJIS.Annotations;

package Example is

  type T is tagged null record;

  function Primitive_That_Cannot_Be_Bounded return T;
  pragma Annotate (AJIS, Bind, Primitive_That_Cannot_Be_Bounded,
                   "Allow_Java_Creation");

  function F return Integer;

end Example;

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file.

with AJIS.Annotations; use AJIS.Annotations;

with Example;

package Config is

  function P return Hello.T renames Hello.Primitive_That_Cannot_Be_Bounded;
  pragma Annotate (AJIS, Bind, P, "Allow_Java_Creation");

end Config;

5.6. Allowing Java child types even if the primitive cannot be bounded

If a primitive of a tagged record cannot be bounded, use “Allow_Java_Child_Types” or “Allow_Java_Creation_And_Child_Types” qualifier of the Bind AJIS Annotate pragma to avoid having java child type removed because this primitive cannot be bounded.

pragma Annotate (AJIS, Bind, <identifier>, "Allow_Java_Child_Types");
pragma Annotate (AJIS, Bind, <identifier>,
                 "Allow_Java_Creation_And_Child_Types");

The <identifier> argument denotes the Ada entity to take into account as unbounded primitive.

Example:

with AJIS.Annotations; use AJIS.Annotations;

package Example is

  type T is tagged null record;

  function Primitive_That_Cannot_Be_Bounded return T;
  pragma Annotate (AJIS, Bind, Primitive_That_Cannot_Be_Bounded,
                   "Allow_Java_Child_Types");

  function F return Integer;

end Example;

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file.

with AJIS.Annotations; use AJIS.Annotations;

with Example;

package Config is

  function P return Hello.T renames Hello.Primitive_That_Cannot_Be_Bounded;
  pragma Annotate (AJIS, Bind, P, "Allow_Java_Child_Types");

end Config;

5.7. Disabling function alias creation on name clash

If a function alias creation should be removed to workaround a name clash problem, set False value to the “Function_Alias” AJIS Annotate pragma to disable function alias creation for the corresponding procedure.

Tip

“Rename” AJIS Annotate pragma can also be used to workaround this problem. (see Dealing with Name Clashes)

pragma Annotate (AJIS, Function_Alias, <identifier>, False);

The <identifier> argument denotes the Ada entity to take into account as procedure that will not have a function alias created.

Example:

with AJIS.Annotations; use AJIS.Annotations;

package Example is

  type T1 is tagged null record;
  type T2 is tagged null record;

 procedure My_Procedure (O1 : T1; O2 : out T2);
 pragma Annotate (AJIS, Function_Alias, My_Procedure, False);

end Example;

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file.

with AJIS.Annotations; use AJIS.Annotations;

with Example;

package Config is

 procedure P (O1 : T1; O2 : out T2)
    renames My_Procedure (O1 : T1; O2 : out T2);
 pragma Annotate (AJIS, Function_Alias, P, False);

end Config;

5.8. Memory Model

An object on the Ada side – a so-called native object – is accessed in Java through a proxy object: an instance of a class (the proxy class) generated from the Ada object’s type.

The proxy object contains a reference to the native object. Invoking a member function on the proxy object results in a call of a native subprogram on the native object. The native object may be either allocated or declared.

This section explains the implications of this model on the usage of the generated binding.

5.8.1. Requirements for Non-null Parameter Values

If an Ada subprogram’s formal parameter is not an access parameter (i.e., it has in, in out or out mode), then invoking the corresponding Java method requires a non-null reference to a proxy object. For example:

package P is
   type T is null record;

   procedure Proc (V : out T);
end P;

results in the following Java classes:

public class T {
   ...
}

public final class P_Package {
   public static Proc (T V) {
      ...
   }
   ...
}

You need to ensure that the V parameter passed to Proc is not null. Hence, the following will throw an exception, since v is implicitly initialized to null:

T v;
P_Package.Proc (v);

A simple way to provide a non-null reference is to initialize it to an allocated object:

T v = new T ();
P_Package.Proc (v);

5.8.2. Allocating Ada Objects from Java

If A is an Ada type that is mapped by ada2java to a Java class J, then the execution of a Java constructor J() will create two objects:

  • A Java object (the ‘proxy’ object) of class J, allocated on the Java heap and subject to Garbage Collection by the JVM, and
  • An Ada object of type A, allocated on the Ada heap, referenced from the proxy object.

5.8.3. Automatic Creation of Native Objects

Under certain circumstances, the generated Java code may construct extra native objects on the Ada heap. To help explain this, here is an example where such allocation is not needed, namely, a function returning an access value:

package P is
   type T is record ... end record;

   type T_Acc is access all T;

   function Create_T_Acc return T_Acc;
end P;

The generated code will look like:

public class T { ...}

public final class P_Package {
   final public T Create_T_Acc () { ...}
   ...
}

On the Java side, the method Create_T_Acc returns a proxy object that contains the value of the pointer returned by the call of the Ada function. So the user can write:

T v = P_Package.Create_T_Acc ();

and then access the data of v. Note that all the standard precautions that apply in using an Ada pointer have to be taken in this case as well. In particular, after the object has been freed on the Ada side, there should be no further references to it from Java. The Ada programmer needs to document how the returned value should or should not be used, and the Java programmer needs to adhere to these guidelines.

However, a function returning a record rather than an access value raises additional issues:

package P is
   type T is record ... end record;

   function Create_T return T;
end P;

results in a default class similar to the one in the previous example:

public final class P_Package {
   final public T Create_T () { ... }
   ...
}

The Java programmer can write:

T v = P_Package.Create_T ();

But in this case, the value returned by the function is not a pointer. A new Ada native object is automatically allocated on the heap, initialized to a copy of the value returned by the Ada function, and is referenced through the proxy object constructed by the Java method Create_T. This is similar to the Ada situation where calling Create_T_Acc would not involve a copy of a T object, whereas Create_T would.

Because of implementation constraints there may be more than one copy involved in the call of Create_T (but only one native object will be created on the heap). This will be discussed further in Clone and Copy Semantics.

5.8.4. Native Ownership

As illustrated above, native objects may be created automatically by Java methods corresponding to Ada subprograms. This raises the issue of when/how such objects are to be deallocated.

In general, the ada2java approach is based on the following principles:

  • The environment (Java or Ada) that allocates an object is responsible for its deallocation;
  • In Java, all deallocation is performed implicitly, by the Garbage Collector;
  • In Ada, the programmer is responsible for manually deallocating the objects;
  • Dangling references should be prevented; i.e., the Ada object should not be deallocated as long as there are still live references to the object.

A native object created from Java is said to be owned by its proxy object. It has been created by the Java program, and it must be freed by the Java environment.

Such an object is tightly linked to its proxy – when the proxy object doesn’t exist (i.e. is garbage collected), the native object becomes inaccessible. [1]

The native object deallocation will occur automatically, when the Java proxy is garbage collected (more specifically through the implementation of the finalize method).

A native object not owned by a proxy – for example, one that has been obtained from the Ada API through an Ada pointer – will not be deleted automatically.

The ownership state of a native object may be queried through the getOwner method.

This function returns a value from the enumeration com.adacore.ajis.IProxy.Owner, either NATIVE or PROXY, specifying who is responsible for managing the memory. For example after these assignments:

T v1 = new T ();
T v2 = P_Package.Create_T_Acc ();
T v3 = P_Package.Create_T ();

the following relationships hold:

v1.getOwner () PROXY
v2.getOwner () NATIVE
v3.getOwner () PROXY

Native objects referenced by v1 and v2 will be deallocated when the corresponding Java proxy is garbage-collected.

You may change the owning attribute of a referenced native object, through the setOwner method of proxy classes.

This should be used very carefully, as it may generate memory leaks or corruption. Doing the following:

T v1 = new T ();
v1.setOwner (Owner.NATIVE);

will deactivate the object deallocation on finalization. The Java programmer becomes responsible for explicitly deallocating the native object.

Note that, while moving the owning from PROXY to NATIVE is a relatively safe operation, raising the possibility of memory leaks but not object corruption, moving in the opposite direction should be done with great care. A object managed by the NATIVE side may be deallocated by the Ada application at any moment, or be declared as the field of another object or even on the native stack in situations such as callbacks. If it’s not clear where the object is coming from, cloning it (resulting automatically into an object managed by the proxy) is often the best solution.

As will be described below, only objects that are known to be managed though pointers can have their ownership changed. An object obtained, for example, from a global variable or field will need to retain its known ownership.

5.8.5. Object Allocators

The generated code keeps track of how native objects have been obtained, and restricts their operations accordingly. Three possible allocators are recognized:

DYNAMIC
Such objects are created either through bound constructors, accessed from native access types, or through an automatic copy from the bound code. The ownership of dynamic objects can be changed.
STATIC
Such objects are accessed from global variables, fields, or callback parameters that are not access types. The ownership of these objects is always NATIVE and cannot be changed.
UNKNOWN
In some cases, for example on copying values of tagged types, it is not possible to determine whether the object has been allocated statically or dynamically. In such cases you can access the allocator through which an object is referenced using the IProxy.getAllocator () function.

5.8.6. Restrictions on Proxy-Owned Objects Passed to Subprograms

Passing a (reference to a) native object as an actual parameter to an Ada subprogram whose corresponding formal parameter is either an access parameter or of an access type could in some cases result in a dangling reference to the native object, whereas in other situations it might be harmless. In order to provide the desired safety while allowing the needed generality, ada2java‘s approach is similar to the way that the Ada standard supplies both the 'Access and 'Unchecked_Access attributes for composing pointers to data objects.

In summary, when the Java side is responsible for pointer memory management (i.e. the object is owned or it is accessed through 'Access or 'Unrestricted_Access), then an exception will be thrown in Java on an attempt to pass a reference to a native object as an actual parameter when the formal is of a named or anonymous access type. However, this check can be suppressed either locally (for a given formal parameter) or globally (for all calls).

Here is a summary of the troublesome scenario:

  • A reference to a proxy object is passed as a parameter to a Java method, and the reference to the corresponding native object is then passed to a native Ada subprogram whose formal is an access parameter or of an access type;
  • The Ada subprogram copies the parameter to a global variable and ultimately returns;
  • The proxy object ultimately becomes inaccessible and is garbage collected by the JVM;
  • The native object is deallocated as an effect of the proxy object’s finalization;
  • The deallocated object is still designated by the global variable.

To prevent this, an exception is thrown at step 1 above.

Here is an example:

-- Original Ada package
package P is
   type T is
      record
         A, B : Integer;
      end record;

   type A_T is access all T;

   G : A_T;

   procedure Set_G (V : A_T);
end P;

package body P is
   procedure Set_G (V : A_T) is
   begin
      G := V;
   end Set_G;
end P;
// Java statements
T v = new T ();
P_Package.Set_G (v); // Throws an exception

If the invocation of Set_G did not throw an exception, G would become a dangling pointer when the proxy object referenced by v is garbage collected.

However, if the invocation of Set_G did not cause an assignment of its formal parameter V to a global variable, then passing v as an actual parameter would be harmless. ada2java allows the Ada API to enable such uses, and thus to deactivate the check:

pragma Annotate (AJIS, Assume_Escaped, <Condition>, <Subprog>, <Formal_Param>)

where:

  • <Condition> is either True or False
  • <Subprog> is the name of the Ada subprogram
  • <Formal_Param> is the affected formal parameter, expressed as a String literal

When <Condition> is False, the Ada subprogram is responsible for ensuring that the formal parameter is not copied to a global variable, or used to set a field of a structure that will be used outside of the scope of the subprogram. When <Condition> is True, an exception is thrown when a proxy object is passed as an actual parameter to the <Formal_Param> parameter of <Subprog>.

Here is an example:

package P is
   type T is
     record
        A, B : Integer;
     end record;

   type A_T is access all T;

   G : A_T;

   procedure Safe_Set_G (V : A_T);
   pragma Annotate (AJIS, Assume_Escaped, False, Safe_Set_G, "V");
end P;

package body P is
   procedure Safe_Set_G (V : A_T) is
   begin
      G := new T'(V.all);
   end Safe_Set_G;
end P;
*// Java statements*
T v = new T ();
P_Package.Safe_Set_G (v); *// OK*

The invocation Safe_Set_G (v) is safe since the Ada subprogram does not let the formal parameter ‘escape’.

The ‘escaped’ checks can be globally activated through the ada2java switch --assume-escaped, and globally deactivated through the switch --no-assume-escaped.

The default is --assume-escaped. An explicit pragma overrides the global configuration switch.

Note that canceling escape checks should be done with great care, as there is no way to ensure that no escaping has occurred. In many situations, the programmer’s intent is to create an object on the Java side and then store the object on the native side. In such a case, canceling proxy ownership through the IProxy.setOwner method will have the desired effect, for example:

// Java statements
T v = new T ();
v.setOwner (Owner.NATIVE);
P_Package.Set_G (v); // OK

5.8.7. Parameter Mode Documentation

ada2java documents the mode of the parameters generated by the binding. This mode is only documented for proxies, not scalar values. The following modes are possible:

passed by value
The object is passed by value. In calls from Java to Ada, it means that the proxy cannot be null. Proxy or native ownership doesn’t matter. In callbacks implemented in Java, proxies for these parameters will be natively-owned and statically allocated. For returned value, the returned Ada object is copied to the returned proxy.
passed by reference (escapable)
The object is passed by reference, and potentially escaped by the Ada code. The object has to be natively owned and dynamically allocated.
passed by reference (non escapable)
The object is passed by reference, but is known not to be escaped by the native code. Any proxy can by passed to parameters of that type. When implementing a callback with a parameter of this type, or for returned values, the proxy will be natively owned and dynamically allocated.
passed by reference (static)
The object is passed by reference, but its reference is coming from a location place, typically a global variable or a field. This only applies to returned values. The returned proxy will be natively owned and statically allocated.

5.9. Aliasing

As explained in the previous chapter, Ada object are managed through Java proxies. The creation of such proxies is obtained through an access type. For example, in the following code:

package P is
   type T is
     record
        A, B : Integer;
     end record;

   V1 : aliased T;

   type T2 is record
      V2 : aliased T;
   end record;

   type A is array (Integer range <>) of aliased T;

   V3 : aliased A (1 .. 10);
end P;

the binding generator will generate accessors to V1, V2 and V3 so that fields of T can be modified directly. It’s possible to write:

V1 ().A (1);

T2 v = new T2 ();
v.A (2);

V3 ().Get_Element_At (10).A (3);

Accessing variables generate a proxy pointing directly to the address in memory. These proxies can be manipulated on their own, as any other, for example:

T ptr = V1 ();
ptr.A(1);

In the above example, ptr is a Java proxy containing a pointer to the global variable V. Such a pointer would typically be obtained through a 'Access applied on the Ada variable V1.

It’s sometimes unconvenient to declare aliased data just for the purpose of binding generation. GNAT offers a mechanism to retreive an access to any piece of data, 'Unrestricted_Access. The use of such method is however dangerous. On certain architectures, e.g. sparc-solaris, forcing to retreive an access on a missaligned data will issue a program crash. In order to avoid that, by default, ada2java does not generate such accesses and create proxies only on aliased data.

This behavior can be deactivated with the ada2java argument --unaliased-access, which will allow access to unaliased data. Making data aliased should always be prefered to the use of that option.

5.10. Thread Safety

By default, the generated Java code is thread-safe, with locking logic that prevents multiple Java threads from accessing native methods at the same time. In summary, a Java method in a proxy object acquires a lock (a binary semaphore) before invoking the corresponding native method, and releases the lock when the native method returns (either normally or abnormally). The semaphore is global versus per Ada package; e.g. different Java threads are not allowed to invoke native methods simultaneously even if the native methods correspond to subprograms from different packages.

Even if the Java application does not explicitly create any threads, there are still two threads – the main (user) thread and the garbage collector; thus locking is needed in this case also.

The default locking behavior is not always appropriate, however. In particular, if the native code is using tasking, with mutual exclusion enforced on the Ada side, then there is no need for locking in the Java code (and indeed such locking could have undesirable consequences including deadlock).

User control over the generation of locking code is obtained through the following pragma:

pragma Annotate (AJIS, Locking, <Subprogram>, <LockControl>);

where <Subprogram> is the subprogram name, and <LockControl> is one of Disable, Check and Protect.

Protect
Default setting. The generated code automatically brackets each invocation of the named native <Subprogram> within a lock ... unlock region. If a thread t1 invokes <Subprogram> while some other thread t2 holds the lock, then t1 will be queued until the lock is released. Thus simultaneous calls of native methods are permitted but will entail queuing.
Disable
The generated code contains no locking around invocations of <Subprogram>, and one thread may invoke <Subprogram> while some other thread is executing a native method (even one whose LockControl is Protect.
Check
The generated code contains no locking around invocations of <Subprogram>, and the application must ensure that any such invocation is within a lock ... unlock region. An exception is thrown if a thread invokes <Subprogram> without holding the lock.

The following examples illustrate the multithreading behavior and the effects of the <LockControl> argument to the pragma.

package P is
   procedure P1 (I : Integer);
   pragma Annotate (AJIS, Locking, P, Disable);

   procedure P2 (I : Integer);
   pragma Annotate (AJIS, Locking, P, Disable);
end P;

Concurrent invocations of P1 and P2 are allowed (i.e., locking code is not generated automatically).

Code protected by the lock can be provided by hand by the Java developer. The lock is created in the library class generated for the binding, <base_package>.Ada2Java.Library, under the identifier lock. Thus:

import base.P_Package;
import base.Ada2Java.Library;

public class Main {
   public static void main (String [] args) {
      Library.lock.lock ();

      try {
         P_Package.P1 (0);
         P_Package.P2 (0);
      } finally {
         Library.lock.unlock ();
      }
   }
}

Following standard Java coding style, the lock / unlock logic should always appear in a try ... finally block, in order to release the lock even if an unexpected exception is propagated.

In certain cases, locking is required but the logic is more complex than simply protecting each native method invocation with a semaphore. For example, it may be necessary to invoke a sequence of native methods as an atomic action. This effect can be achieved through the Check setting for LockControl:

package P is
   procedure P1 (I : Integer);
   pragma Annotate (AJIS, Locking, P1, Check);

   procedure P2 (I : Integer);
   pragma Annotate (AJIS, Locking, P2, Check);
end P;

The Java program has to acquire the library lock before attempting any native call. Invoking P1 or P2 outside of a section protected by the lock will throw a Java exception.

The locking behavior can be changed globally through the ada2java --[no-]locking switch. More specifically, here are the permitted values for this switch:

--locking-protect
Default setting; globally sets LockControl as Protect for all subprograms.
--locking-check
Globally sets LockControl as Check for all subprograms.
--no-locking
Globally disables locking (i.e., sets LockControl as Disable for all subprograms).

Note that an AJIS Locking pragma takes precedence over the global switch.

The finalize method invoked during garbage collection does not correspond to a subprogram from the Ada package that is input to ada2java, and it is not affected by the --[no-]locking switch. Instead, the locking logic used for the finalize call of Java proxies during garbage collection is determined by the --[no-]locking-finalize switch:

--locking-finalize-protect
Default setting; sets LockControl as Protect for finalize.
--locking-finalize-check
Sets LockControl as Check for finalize.
--no-locking-finalize
Disables locking (i.e., sets LockControl as Disable for finalize)

A typical usage of these two switches would be to set Check as the LockControl for all subprograms, and Protect as the LockControl for finalize methods:

ada2java --locking-check --locking-finalize-protect p.ads

5.11. Proxies and Native Object Equality

Proxy classes are generated with an equals implementation that calls the corresponding Ada = operation. For example:

package P is
   type T is
      record
         F : Integer;
      end record;
end P;

The proxy class can be used as follows:

T v1 = new T ();
T v2 = new T ();
v1.F (0); // "setter" method for field F
v2.F (0); // "setter" method for field F

and now the result of v1.equals (v2) is true. This corresponds to ‘shallow’ equality, in contrast with == which tests pointer identity.

A new proxy is created each time a native function returns a pointer. For example:

package P is
   type T is null record;
   type A_T is access all T;
   G : A_T := new T;
   function Return_G return A_T;
end P;

package body P is
   function Return_G return A_T is
   begin
      return G;
   end Return_G;
end P;
AT p1 = P_Package.Return_G ();
AT p2 = P_Package.Return_G ();

Now p1 == p2 is false, since a new proxy is created by each function return, but p1.equals (p2) is true,

The association between a proxy and its native object is lost when the proxy is passed to a native method. For example:

package P is
   type T is null record;
   type A_T is access all T;
   function Return_This (This : A_T) return A_T;
end P;

package body P is
   function Return_This (This : A_T) return A_T is
   begin
      return This;
   end Return_G;
end P;
T v = new T ();

Now the result of (v == P_Package.Return_This (v)) is false

Java reference equality has special semantics in the case of cross-language inheritance, due to the use of a shadow native object – see Shadow Object Equality for more details.

5.12. Clone and Copy Semantics

A proxy class generated from a non-limited Ada type includes a clone method. The base class for all proxies, AdaProxy, implements the Cloneable interface, and defines a public method clone.

The clone method performs a ‘shallow copy’ of all the fields, except for the native object reference. The native object reference in the cloned proxy is a pointer to a newly allocated Ada object. This new native object is itself a shallow copy of the original native object.

Thus cloning a proxy does not result in the sharing of the native object by the original proxy and the cloned proxy. The latter points to a new native object. This behavior is needed to avoid a dangling reference (to the native object) when the original proxy is garbage collected.

If the proxy class corresponds to a limited type, then the generated clone method will throw an exception.

Additional semantics for clone and copy, in connection with cross-language inheritance, are covered below (see Shadow Object Cloning).

5.13. Cross-Language Inheritance

This section discusses a number of issues related to cross-language inheritance; i.e., defining a Java class as an extension of a proxy class for an Ada tagged type.

5.13.1. Inheriting from a Java Proxy

As explained in Tagged Types, ada2java maps an Ada tagged type to a non-final Java proxy class. You can extend this class in Java. For example:

package P is
   type T is tagged null record;

   procedure Prim (V : T);
end P;

results in the following Java class:

public class T {
   public void Prim () {
      ...
   }
   ...
}

which may be extended, with the instance method overridden:

public class T_Child extends T {
   public void Prim () {
      ...
   }
}

An object of the subclass T_Child is also a proxy; constructing such an object allocates a native object, referred to as a ‘shadow native object’. Its properties will be described below (The Shadow Native Object).

The Java program can then invoke the Prim method, with standard Java dispatching behavior. For example:

T v1 = new T ();
T v2 = new T_Child ();

v1.Prim (); // Will call the native Prim
v2.Prim (); // Will call the overridden Java Prim

5.13.2. Cross Language Dispatching from Ada

A method overridden in Java can be called in Ada using the usual Ada dispatching mechanism. For example:

package P is
   type T is tagged null record;

   procedure Prim (V : T);

   procedure Call_Prim (V : T'Class);
end P;

package body P is

   procedure Prim (V : T) is
   begin
      --  Native Prim implementation;
   end Prim;

   procedure Call_Prim (V : T'Class) is
   begin
      Prim (V); -- Dispatching call.
   end Call_Prim;

end P;

The invocation of Prim in Call_Prim is dispatching, and the Prim for the type of the actual parameter will be called. In Java, this procedure can be used in conjunction with a cross-language extension of T, e.g.:

class T_Child extends T {
   public Prim () {
      ...
   }
}

T v = new T_Child ();
P_Package.Call_Prim (v);

The Java program is invoking the native Ada Call_Prim procedure, which in turn dispatches to the Java method Prim in T_Child.

5.13.3. The Shadow Native Object

This section describes in more detail the semantics of the shadow native object.

5.13.3.1. Basic Properties

As seen above, cross-language dispatching is supported; an Ada dispatching call may result in the invocation of a Java method on a proxy object. This is possible because of the shadow native object concept.

For any tagged type T declared in a package spec that is input to ada2java, a new type is automatically generated that extends T with a component that references a proxy object. The Ada declaration is:

type Shadow_T is new T with
   record
      Link_To_Proxy : Java_Object;
   end record;

If T_Child is a Java class that extends T, then constructing an instance of T_Child will create a native Shadow_T object instead of a regular T object. This type overrides every controlling primitive of T, and delegates the dispatching to the Java side. If a method corresponding to an Ada primitive operation is not overridden in Java, then the subprogram from the parent Ada type will be automatically called.

The use of a shadow object introduces a tight relationship between a Java proxy and its Ada native object. Basically, there is a roundtrip dependency between the two, so that a Java proxy object is associated with a unique Ada shadow native object and vice versa.

This has several non-trivial implications that are described in the sections below.

5.13.3.2. Memory Management

The reference from the shadow native object to the Java object is called a global reference. [2]

There are two kinds of global references: regular (usually simply referred to as global references) and weak. A regular global reference prevents the object from being garbage collected, whereas a weak global reference does not.

When a native object is owned by its proxy, the proxy is responsible for releasing the native memory. In this case, the reference from the native object to the proxy is a weak global reference: garbage collection will not be prevented, and both the proxy and then the native object will be released upon collection.

However, when the native object is owned by the native side, then the native object may continue to exist even if the proxy has become inaccessible from Java. In such a case, a global reference is used, so that the garbage collector is prevented from collecting the Java object. Such a global reference will be automatically released when the Ada object is actually deallocated.

Forcing the deallocation of the native object through IProxy.deallocateNativeObject will release the global reference as well.

Switching the owner of the native object between NATIVE and PROXY will switch the reference from a regular global reference to a weak one.

In order to avoid a potential memory leak, the link between the shadow native object and the Java proxy has to be broken manually when the shadow native object is no longer needed on the Ada side. This may be done in two ways:

  • Deallocating the native shadow object, from Ada.
  • Invoking the unlink method on the proxy, from Java

5.13.3.3. Shadow Object Equality

Java reference equality (==) is not consistent with native pointer equality, but Java object equality (equals) is. That is, if A and B are two native pointers where A = B, then on the Java side A.equals(B) is true but a==b is false.

However, since the association between shadow object and proxy is one-to-one, proxy equality is meaningful. For example:

package P is
   type T is tagged null record;
   type T_Access is access all T'Class

   procedure Identity (Item : T_Access) return T_Access;
   -- Returns Item as its result
end P;

On return from Identity, the ‘glue code’ between Java and Ada will check if the returned value is a shadow native object and, if so, will return the corresponding Java proxy instead of creating a new one. Hence the following fragment

public class T_Child extends T {
}

T v = new T_Child ();

will result in the expression P_Package.Return_This (v) == v delivering true.

5.13.3.4. Shadow Object Cloning

Cloning from Java will result in copying a native object. In the case of a shadow native object, there will be a new shadow object as well, referencing the newly created java proxy, thus preserving the one-to-one relationship between the shadow native object and the proxy.

However, there are cases where copies are made from Ada as well:

package P is
   type T is tagged null record;
   type T_Access is access all T'Class

   procedure Duplicate (Item : T_Access) return T_Access;
end P;

package body P is

   procedure Duplicate (Item : T_Access) return T_Access is
   begin
      First_Copy  : T'Class  := Item.all;
      Second_Copy : T_Access := new T'Class'(Item.all);
   begin
      return Second_Copy;
   end Duplicate;

end P;

Calling Duplicate from Java will lead to two shadow object copies, one on the stack (First_Copy) and one on the heap (Second_Copy.all). As explained earlier, the link between the shadow native object and the proxy will be deleted when the Ada object is deallocated, at the exit of Duplicate for the First_Copy variable.

The Second_Copy proxy created in the copy process will be returned by the Java method corresponding to the Duplicate procedure.

Note that a proxy created by such a copy does not own its Ada native object.

Proxy cloning from Ada does not involve a clone invocation from the Java extended object. If the Java code needs to perform a deep copy in the Java proxy, the method void proxyCloned (IProxy initialObject) should be overridden instead. The default clone implementation of the generated Java classes will call this method as well.

Limitation: when proxyCloned is called from Ada, the link between proxy and native object is not yet established. Thus proxyCloned is not allowed to invoke any native methods. The Ada type may be derived from Finalization.Controlled, and Adjust may be overridden, to work around this limitation.

Note that multiple copies – and thus repeated proxy creation – may be involved when a (non-access) shadow object is returned. For example:

package body P is

   function Identity (Item : T_Access) return T'Class is
   begin
      return Item.all;
   end Identity;

end P;

Due to internal machinery, three copies may be needed in the implementation of the return from this function when called from Java. If it is necessary to ensure that only one copy is performed, then the Ada function should be written as a wrapper for an Ada access-returning function.

5.13.4. Controlled Types

A class that is generated from an Ada controlled type may be extended in Java, with overriding versions of the Adjust, Finalize, and/or Initialize methods.

A current limitation is that Initialize cannot be called on a shadow object, e.g.:

package P is

   type T is new Controlled with null record;

   procedure Initialize (This : in out T);

end P;

can be used in:

class T_Child extends T {

   void Initialize () {

   }

}

T v = new T_Child ();

But the overridden Initialize will not be called by the constructor.

This limitation will be removed in a future release.

5.14. Managing Attachment to Java Proxies

By default, subprograms (except controlling primitives) are not attached – they are placed in the default class <Ada_Package_Name>._Package.

However, when all of the following conditions are met, a subprogram is attachable to the class corresponding to its first parameter:

  • The first parameter of the subprogram has one of the following forms:
    • A private type or a record type,
    • An access type (of mode in) designating a private or record type, or
    • An access parameter designating a private or record type.
  • The type of this parameter – or of the designated type if an access parameter – is declared in the same package spec as the subprogram.

A subprogram that meets these criteria can be mapped to a method defined in the class corresponding to the first parameter’s type, and the value of this first parameter will come from the hidden parameter this.

This attachment is activated by the annotation pragma Attached:

pragma Annotate (AJIS, Attached, <Condition>, <Subprogram>);

Example:

package Example is
   type Rec is null record;
   procedure Proc (V : Rec; I : Integer);
   pragma Annotate (AJIS, Attached, True, Proc);
end Example;

will map to the class

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

Attachment policies may be globally turned on / off using the following switches:

--[no-]attach-parameter

Activates or deactivates ‘best-effort’ attachment to the class corresponding to the first parameter. When this is set, ada2java will try to perform subprogram attachment whenever possible. Default is --no-attach-parameter.

--[no-]attach-access

Activates or deactivates attachment for access type. When activated, subprograms with an access type (named or anonymous) for their first parameter will be attached. (Note, however, that such attachment prevents passing a null value, since this is always the implicit parameter.) Default is --no-attach-access.

--[no-]attach-controlling

Activates or deactivates attachment of controlling primitives. This is required for cross language inheritance. Default is --attach-controlling.

--[no-]attach-constructor

Activates or deactivates attachment of functions as static factory method. Default is --no-attach-constructor.

Example:

package Example is
   type Rec is null record;
   function Get_Default return Rec;
end Example;

will map to the class

public class Rec extends com.adacore.ajis.internal.ada.AdaProxy {
  ...
  public static Rec Get_Default()
  ...
}

--[no-]attach-variables

Activates or deactivates attachment of package’s variables as static getter/setter method of bound java class. Default is --no-attach-variables.

Example:

package Example is
   type Rec is null record;
   Instance : Rec;
end Example;

will map to the class

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

--[no-]attach-ada2005

Activates or deactivates attachment based on applicability of Ada 2005 prefix notation. With the --attach-ada2005 switch, ada2java will attempt to attach a subprogram (define it in the class corresponding to the initial parameter) that would otherwise be placed in the default package, if it can be invoked via Ada 2005 prefix notation. Default is --no-attach-ada2005.

In the example below, attachment is requested for everything except noncontrolling initial access parameters:

ada2java --attach-parameter --no-attach-access p.ads

Pragma Attached takes precedence over the global switch.

5.15. Exceptions propagation

Exceptions raised from Ada are translated into instances of the relevant descendant of class com.adacore.ajis.NativeException (see Exceptions) and propagated to Java.

Exceptions raised from a Java callback are translated back to the original Ada exception - or to Java_Exception declared in the AJIS.Java package, and propagated to Ada.

5.16. Allowing limited types Java derivation

Limited tagged record and interface can be allowed to be derived in Java using the Allow_Derivation AJIS pragma.

Warning

limited type Java derivation will fail if limited type is returned by reference. Consider switching to return of access type to allow Java derivation.

Example:

package Example is
  type Tagged_Record_Type is tagged limited null record;
  pragma Annotate (AJIS, Allow_Derivation, Tagged_Record_Type, True);
end Example;

This will result in a TaggedRecordType java class that can be extended. Extended classes can be fully handled by ada code.

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file, applying it to an Ada entity that is itself a subtype declaration.

Example:

with Example;
package Renamings is
  subtype Tagged_Record_Type is Example.Tagged_Record_Type;
  pragma Annotate (AJIS, Allow_Derivation, Tagged_Record_Type, True);
end Renamings;

5.17. Disabling Java Iterator For Ada Collections

Ada collections java iterators generation can be disabled/enabled for a specific collection using the Allow_Iterator AJIS pragma.

Example:

package Example is
  type Record_Type is null record;
  pragma Annotate (AJIS, Allow_Iterator, Record_Type, False);
end Example;

This will result in a RecordType java class not implementing Iterable interface.

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file, applying it to an Ada entity that is itself a subtype declaration.

Example:

with Example;
package Renamings is
  subtype Record_Type is Example.Record_Type;
  pragma Annotate (AJIS, Allow_Iterator, Record_Type, False);
end Renamings;

5.18. Disabling Java Iterator Optimization For Ada Collections

Ada collections java iterators optimized implementation can be disabled/enabled for a specific collection using the Allow_Iterator_Optimization AJIS pragma.

Example:

package Example is
  type Record_Type is null record;
  pragma Annotate (AJIS, Allow_Iterator_Optimization, Record_Type, False);
end Example;

This will result in a RecordType java class implementing a non optimized Iterable interface (requiring 3 ada calls per collection element).

Adding a pragma to an Ada package specification is not always practical, and indeed may be impossible if the specification is from an external library. With GNAT-AJIS, you can provide the pragma in a separate Ada file, applying it to an Ada entity that is itself a subtype declaration.

Example:

with Example;
package Renamings is
  subtype Record_Type is Example.Record_Type;
  pragma Annotate (AJIS, Allow_Iterator_Optimization, Record_Type, False);
end Renamings;

Footnotes

[1]

As will be described below, cloning the proxy object has special semantics so that the native object gets copied, not only its reference.

[2]

An explanation of global references may be found in Liang’s {The Java Native Interface Programmer’s Guide and Specification}, Chapter 5.