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, andAn 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:
|
|
|
|
|
|
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 eitherTrue
orFalse
<Subprog>
is the name of the Ada subprogram<Formal_Param>
is the affected formal parameter, expressed as aString
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 alock
…unlock
region. If a threadt1
invokes<Subprogram>
while some other threadt2
holds the lock, thent1
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 whoseLockControl
isProtect
.Check
The generated code contains no locking around invocations of
<Subprogram>
, and the application must ensure that any such invocation is within alock
…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
forfinalize
.--locking-finalize-check
Sets LockControl as
Check
forfinalize
.--no-locking-finalize
Disables locking (i.e., sets LockControl as
Disable
forfinalize
)
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, orAn 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, sincethis
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
As will be described below, cloning the proxy object has special semantics so that the native object gets copied, not only its reference.
An explanation of global references may be found in Liang’s {The Java Native Interface Programmer’s Guide and Specification}, Chapter 5.