Authors: Lars-Åke Fredlund ([email protected]). JavaErlang is a library that attempts to facilitate the communication between Java and Erlang nodes, implemented as an additional layer on top of the JInterface Java interface. The library makes heavy use of the Java reflection capability, provided in the package java.lang.reflect.
The primary functionality is that the library provides functions for creating and interacting with Java objects without the need to write any boilerplate code.
An example:
$ erl -sname anyname
> application:ensure_started(java_erlang).
> {ok,NodeId} = java:start_node().
> Int10 = java:new(NodeId,'java.lang.Integer',[10]).
> String10 = java:call(Int10,toString,[]).
The above code starts a Java node with a specified name (note that the name itself does not matter, just that we start erlang with the -sname
or -name
option), and establishes
a connection to it. Then, a Java integer storing the value 10 is created,
and finally a Java string representing "10" is returned
from the Java integer just created.
To make your Java classes (and Jar files) visible to the library
the option add_to_java_classpath
should be provided to
java:start_node/1
. An example:
{ok,NodeId} = java:start_node([{add_to_java_classpath,["classes"]}]).
This adds the directory classes
to the classpath of the started Java interpreter
JavaErlang requires rebar3 for building and testing. See here for getting started with rebar3.
To compile, execute the following command:
$ rebar3 compile
After compilation Erlang beam files will be left in the directory _build/default/lib/java_erlang/ebin/.
Should you wish to install the JavaErlang library in the standard
Erlang library structure, the following commands can be used:
$ erl -pa _build/default/lib/java_erlang/ebin/
> java_erlang_install:install().
To test, execute the following command:
$ rebar3 eunit -v
If the eunit tests timeout, try re-running the test to see if the timeout repeats.
To generate Markdown docs, execute the following command:
$ env ERL_LIBS=$PWD/_build/default/lib/edown rebar3 edoc
The test
directory contains a number of examples. The subdirectory
qc
contains a simple Quviq QuickCheck test model for the Java HashSet
class -- if QuickCheck is installed the class can be verified by
invoking the hashset_test:test()
function. The demo
subdirectory
contains a simple example of using the Swing GUI library from Erlang.
A couple of updates to manage situations where the Java node cannot be started by calling the main method (i.e., by starting a new Java runtime), but should rather be integrated into an existing Java runtime. There are two new (Java) methods:
-
receiveConnection(Level logLevel, String name, String cookie, boolean returnOtpErlangObject)
-
reportAndReceiveConnection(Level logLevel, String ourName, String otherNode, String reportName, String cookie, boolean returnOtpErlangObject)
The first method waits for the Erlang side to initiate a connection attempt
(using java:connect
as before). This works well if the Erlang side somehow
has prior knowledge of the Java node name.
If the Erlang side cannot obtain a prior knowledge of the Java node name,
then the second method may be used, which starts by sending a message
to the Erlang process registered under the name reportName,
at the node otherNode, with the name of the new Java node.
Then connection proceeds as for the first method.
The library now supports a facility for directly returning OtpErlangObject
values (i.e., those Java values which the Erlang jinterface library understands), without first mapping them in the Java layer.
This behaviour, which is not the default one, can be enabled
by passing the flag {return_OtpErlangObject,true} to start_node/2
.
Alternatively, using the new Java connection primitives explained above,
the last argument (returnOtpErlangObject
) to the two methods should be true.
The library now provides a facility to construct
Java classes using Erlang, see the java_proxy
module for details.
To disable this
(the option is enabled by default)
provide {enable_proxies,false}
as an option to start_node/2
.
Garbage collection is now implemented. To enable this
(the option is currently disabled by default, but the default
can change without warning)
provide {enable_gc,true}
as an option to start_node/2
.
The code for generating an Erlang module corresponding to
a Java class has been removed in this version of the library.
Thus, Java objects can be created and accessed only
using the functions exported from the java
module.
Java nodes can now be started on remote Erlang nodes --
see the options for the java:start_node/1
function.
The source code of the library is available on GitHub
at
git://github.com/fredlund/JavaErlang.git
.
Precompiled versions of the library are available too.
The Java null value is represented as the Erlang atom null
,
the Java boolean values true and false are represented as the
corresponding atoms true
and false
,
and integer-like types and floating-point-like types are
represented as normal Erlang integers and floats. Arrays are
constructed using the normal Erlang syntax for lists and strings.
Values can be explicitely type cast using the notation
{Type,Value}
. For example, {short,5}
, {char,$a}
,
{{array,char,1},"Hello World"}
.
For constructing arrays it is however better to use a tuple to enumerate
the elements of the array, as is done in the two-dimensional array:
{{array,'int',2},{{1},{2}}}
.
Using a list risks causing JInterface to interprete that list as an Erlang
string, and transmitting it as an Erlang string object (OtpErlangString)
instead of a proper list (OtpErlangList). This might even be considered
a bug in JInterface.
Note that
Java one-dimensional arrays can also be constructed
using the funcion java:list_to_array/3
, and here the use of lists
as arguments is safe.
Examples:
> {ok,NodeId} = java:start_node().
{ok,9231}
> False = java:new(NodeId,'java.lang.Boolean',[false]).
{object,0,9231}
> HelloWorldString = java:new(NodeId,'java.lang.String',[java:list_to_array(NodeId,"Hello World!",char)]).
{object,2,9231}
> Zero = java:new(NodeId,'java.lang.Integer',[0]).
{object,3,9232}
> java:call(Zero,intValue,[]).
0
> java:string_to_list(java:new(NodeId,'java.lang.String',[{{array,char,1},"Hello World"}])).
"Hello World"
> java:string_to_list(java:new(NodeId,'java.lang.String',["Hello World"])).
"Hello World"
Boxing and unboxing of primitive method and constructor arguments is done by the library. Examples:
> {ok,NodeId} = java:start_node().
{ok,9231}
> Zero = java:new(NodeId,'java.lang.Integer',[0]).
{object,3,9232}
> java:call(Zero,equals,[0]).
true
> java:call(Zero,equals,[2]).
false
> java:call(Zero,equals,[0.0]).
false
Primitive values returned from a method are represented as normal Erlang values, whereas objects are returned as objects.
> {ok,N} = java:start_node().
{ok,923}
> Zero = java:new(N,'java.lang.Integer',[0]).
{object,3,923}
> java:call(Zero,intValue,[]).
0
Since intValue returnes an int, a primitive type, the library lets the corresponding Erlang function return an Erlang integer.
A Java exceptions Exc is manifest as an Erlang exception
{java_exception,Exc}. Example:
{ok,NodeId} = java:start_node(),
try java:new(NodeId,'hola',[])
catch {java_exception,Exc} ->
io:format("Exception is of type ~p~n",[java:getClassName(Exc)]),
java:print_stacktrace(Exc)
end.
The option java_exception_as_value
(which can be passed
as an argument to java:start_node/1
) determines whether Java exceptions are
indeed returned as exceptions (the default), as shown in code excerpt above,
or whether they are returned as Erlang values (when the option is set
to true).
An object of class "c" can be
created by calling java:new(NodeId,c,Args)
,
where Args
is the list
of arguments of the constructor.
An example:
{ok,NodeId} = java:start_node(),
I2 = java:new(NodeId,'java.lang.Integer',[2]).
A public method "m" of a Java object "o" of class "c" can be
called using the function call
java:call(o,m,Args)
(or java:call_static(c,m,Args)
if it is
a static, i.e., class method) where Args
is the list
of arguments of the method.
An example:
{ok,NodeId} = java:start_node(),
I2 = java:new(NodeId,'java.lang.Integer',[2]),
I2b = java:new(NodeId,'java.lang.Integer',[2]),
true = java:call(I2,equals,[I2b]).
This code excerpt creates two Java Integers (of value 2),
and checks that the method equals
returns true.
A public field "f" of a Java object "o" of class "c" can be
accessed using the function call
java:get(o,f)
(or java:get_static(c,f)
if it is a class field).
Similarly, the call java:set(o,f,v)
is used to assign the value
v
to the field (and java:set_static(o,f,v)
is used
for class fields).
An example:
{ok,NodeId} = java:start_node(),
Err = java:get_static(NodeId,'java.lang.System',err),
java:call(Err,println,[{int,2}]).
This code excerpt retrieves the Java standard error stream from
the field err
in java.lang.System
, and prints
the integer 2.
Currently only the public methods and fields of Java classes
are accessible. It is likely that in the future we will
provide an option to permit calling methods that are
protected
, i.e., methods callable only from inside a Java package.
A Java node is created by calling the function java:start_node/0 or java:start_node/1. The JavaErlang interface supports communication between one Erlang node and multiple Java nodes, i.e., java:start_node can be called multiple times from the same Erlang process, and the resulting Java nodes are completely separated. Naturally, a Java object residing on one Java node should not be communicated to a different Java node.
A Java node identifier is an Erlang node (global) resource. That is, an Erlang process P1 can pass a Java node identifier to another Erlang process P2, and then P2 can also call Java code on that Java node.
There is a 1-1 mapping between Erlang processes and Java threads. That is, an Erlang process making a call to Java will not block because another Erlang process is making a Java call.
If the Java code fails to respond to a call from Erlang,
and a time limit has been set,
an exception java_timeout
will be thrown.
Note that timeouts can be set on a per-process, per-call basis, using
the function set_timeout/1
.
The source code for the JavaErlang library is generally licensed under the modified 3-clause BSD software license. See individual files for detailed licensing conditions or exceptions.
The library has a number of practical limitations:
-
Speed: the scheme used for communicating between Erlang and Java is quite slow.
-
Inability to use the calling context of a Java call to determine which Java method to invoke.
java |
java_erlang_app |
java_erlang_install |
java_erlang_sup |
java_proxy |
java_resource |
java_to_erlang |