% Copyright (C) 2007-2019 Olivier Boudeville
%
% This file is part of the Ceylan-Myriad library.
%
% This library is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License or
% the GNU General Public License, as they are published by the Free Software
% Foundation, either version 3 of these Licenses, or (at your option)
% any later version.
% You can also redistribute it and/or modify it under the terms of the
% Mozilla Public License, version 1.1 or later.
%
% This library is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU Lesser General Public License and the GNU General Public License
% for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License, of the GNU General Public License and of the Mozilla Public License
% along with this library.
% If not, see and
% .
%
% Adapted from code kindly contributed by EDF R&D.
%
% Authors: Robin Huart (robin-externe.huart@edf.fr)
% Olivier Boudeville (olivier.boudeville@edf.fr)
% Gathering of some convenient facilities for the binding to the Java language.
%
% See java_utils_test.erl for the corresponding tests.
%
% See also: python_utils.erl for a similar binding.
%
-module(java_utils).
% Helper exports:
-export([ get_beam_directories_for_binding/0,
send_oneway/3, send_request/3, execute_request/3,
wait_for_request_result/2, classname_to_bytecode_filename/1,
fully_qualified_classname_to_string/1 ]).
% Design notes:
%
% Even if this module belongs to the Ceylan-Myriad layer, it tries to follow the
% conventions enforced in the upper Ceylan-WOOPER layer regarding method
% management.
% PID associated to a Java mailbox (a Java-based pseudo-process):
-type java_mbox_pid() :: pid().
% Designates a method to trigger on the Java side:
-type method_name() :: atom().
% Designates a oneway to trigger on the Java side:
-type oneway_name() :: method_name().
% Designates a request to trigger on the Java side:
-type request_name() :: method_name().
% The parameters of a method triggered on the Java side:
-type method_parameters() :: [ any() ].
% The parameters of a oneway triggered on the Java side:
-type oneway_parameters() :: [ any() ].
% The parameters of a request triggered on the Java side:
-type request_parameters() :: [ any() ].
% The result from a request that was sent to Java:
-type request_result() :: any().
% The name of a Java package (ex: 'org.foobar.research.someteam'):
-type java_package_name() :: atom().
% The name of a Java package, as a string (ex: "org.foobar.research.someteam"):
-type java_string_package_name() :: atom().
% The name of a Java class (ex: 'Foobar'):
-type java_classname() :: atom().
% The name of a Java class, as a string (ex: "Foobar"):
-type java_string_classname() :: atom().
% Designates as precisely as possible a Java class:
-type java_fully_qualified_classname() :: java_classname() |
{ java_package_name(), java_classname() }.
% The name of a Java source file (ex: "Foobar.java"):
-type java_source_filename() :: file_utils:file_name().
% The name of a Java compiled file (ex: "Foobar.class"):
-type java_bytecode_filename() :: file_utils:file_name().
-export_type([ java_mbox_pid/0,
method_name/0, oneway_name/0, request_name/0,
method_parameters/0, oneway_parameters/0, request_parameters/0,
request_result/0,
java_package_name/0, java_string_package_name/0,
java_classname/0, java_string_classname/0,
java_fully_qualified_classname/0,
java_source_filename/0, java_bytecode_filename/0 ]).
% Implementation notes:
% The actual Erlang-Java binding is obtained thanks to the native Jinterface
% package, typically expected to be found in the
% lib/erlang/lib/jinterface-current-install directory (generally a symbolic link
% specifically created from the base directory of the Erlang installation,
% usually ~/Software/Erlang/Erlang-current-install).
%
% See http://erlang.org/doc/apps/jinterface/jinterface_users_guide.html
% for more information.
%
% On the Java side, the equivalent instance of an Erlang node is an OtpNode
% (approximately a JVM) and messages can be sent and/or received through
% instances of mailboxes (OtpMbox) that are associated to a PID (referred to as
% java_mbox_pid/0).
%
% From the point of view of an Erlang process, such a PID can be treated as if
% it was also an Erlang process.
% Finds the BEAM locations of all the dependencies required for binding to
% Java.
%
-spec get_beam_directories_for_binding() -> [ file_utils:directory_name() ].
get_beam_directories_for_binding() ->
[].
% Sends the specified oneway to the specified Java pseudo-process.
%
-spec send_oneway( java_mbox_pid(), oneway_name(), oneway_parameters() ) ->
void().
send_oneway( MailboxPid, OnewayName, OnewayParameters )
when is_atom( OnewayName ) andalso is_list( OnewayParameters ) ->
Message = { OnewayName, OnewayParameters },
%trace_utils:debug_fmt( "Sending to ~w: ~p.", [ MailboxPid, Message ] ),
% No PID sent, no answer to expect:
MailboxPid ! Message.
% Sends the specified request to the specified Java pseudo-process.
%
-spec send_request( java_mbox_pid(), request_name(), request_parameters() ) ->
void().
send_request( MailboxPid, RequestName, RequestParameters )
when is_atom( RequestName ) andalso is_list( RequestParameters ) ->
% PID sent, as a reply is wanted:
MailboxPid ! { RequestName, RequestParameters, self() }.
% Sends for execution the specified request to the specified Java
% pseudo-process, and collects (synchronously) the corresponding result.
%
-spec execute_request( java_mbox_pid(), request_name(),
request_parameters() ) -> request_result().
execute_request( MailboxPid, RequestName, RequestParameters ) ->
send_request( MailboxPid, RequestName, RequestParameters ),
receive
{ java_request_result, Result } ->
Result
end.
% Receives a message from the Java world, usually in answer to a send_oneway/3
% call having used the same MethodName argument, and tries to match it
% with the different accepted types of messages.
%
-spec wait_for_request_result( java_mbox_pid(), method_name() ) -> any().
wait_for_request_result( MailboxPid, MethodName )
when is_atom( MethodName ) ->
% Waits for the response:
Message = receive
_Msg={ Headers, MethodParameters } when is_tuple( Headers ) andalso
erlang:element( 1, Headers ) == java_message ->
erlang:append_element( erlang:delete_element( 1, Headers ),
MethodParameters )
end,
case Message of
% Return of a successful request:
{ request_completed, _ReceivedData } ->
Message;
% Trace emitted from Java:
TraceMessage = { trace_emitted, TraceType, _TraceFormattedMessage }
when is_atom( TraceType ) ->
TraceMessage;
% Exception raised from Java:
ExceptionMessage = { exception_raised, ExceptionType,
_ExceptionFormattedMessage }
when is_atom( ExceptionType ) ->
ExceptionMessage;
% Catch-all clause for message receiving:
OtherMessage ->
trace_utils:error_fmt( "A message received from a Java (Jinterface)"
" OtpMbox driven by ~w, in answer to '~p', "
"does not respect the expected format: ~p~n",
[ MailboxPid, MethodName, OtherMessage ] ),
throw( { invalid_java_message_received, OtherMessage } )
end.
% Deduces the (root) name of a Java bytecode file from the name of the class it
% implements, according to the naming conventions used by the language.
%
% With Java, both names are identical except the extension, hence we just check
% if the name looks CamelCased, i.e. if at least its first letter is in upper
% case.
%
-spec classname_to_bytecode_filename( java_classname() | string() ) ->
java_bytecode_filename().
classname_to_bytecode_filename( Classname ) when is_atom( Classname ) ->
classname_to_bytecode_filename( text_utils:atom_to_string( Classname ) );
classname_to_bytecode_filename( ClassnameString )
when is_list( ClassnameString ) ->
case text_utils:is_uppercase( ClassnameString ) of
true ->
ClassnameString ++ ".class";
false ->
throw( { java_classname_not_camelcased, ClassnameString } )
end.
% Returns a textual description of specified fully qualified classname.
%
-spec fully_qualified_classname_to_string( java_fully_qualified_classname() ) ->
string().
fully_qualified_classname_to_string( { PackageName, Classname } ) ->
text_utils:format( "class '~s' of package '~s'",
[ Classname, PackageName ] );
fully_qualified_classname_to_string( Classname ) ->
text_utils:format( "class '~s'", [ Classname ] ).