diff options
author | Jonas Smedegaard <dr@jones.dk> | 2011-05-17 21:28:20 +0200 |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2011-05-17 21:28:20 +0200 |
commit | ce38b9acaafe15fd2ba8e94d72f009f62b2d05da (patch) | |
tree | ac18aaa38cb5ab29a9ccfadd632a5038a409fa60 |
Imported Upstream version 0.53
81 files changed, 14836 insertions, 0 deletions
@@ -0,0 +1,176 @@ +Revision history for Perl extension Inline::Java +------------------------------------------------ +0.53 Sun Jan 9 08:15:06 EST 2011 + - Removed PerlNatives extension from the build because it is unmaintained and broken. + It can still be enabled manually using the BUILD_PERL_NATIVES build configuration + option. + - Improved automatic casting. + - Fix with get_source_dir() using the DEBUGGER option. Thanks to Paul Frantz for the fix. + - Other minor bug and doc fixes. + +0.52 Sun Dec 17 20:46:51 EST 2006 + - Fixed JNI on cygwin (many thanks to Eric Rybski for the patch) + - Improved installation. 'make java' is now performed automatically. + - Fixed problems with disappearing exceptions by localizing $@. + - Other minor bug fixes + +0.51 Tue May 23 20:40:07 EDT 2006 + - Several major speed optimizations. + - Introduction of support for I/O mapping between Perl and Java (Inline::Java::Handle) + - Applied patches by Andrew Bruno and Tim Bunce for MAC OSX + - JNI fix for system property passing + (thanks to Brian Gugliemetti and Jason Stelzer) + - Added NATIVE_DOUBLES configuration option to avoid loss of precision + when passing double values between Perl and Java + - New interface for processing callbacks from java to perl. + - Added support for java.lang.CharSequence as a primitive type. Any + Perl scalar passed as a java.lang.CharSequence will instantiate + a java.lang.String on the Java side + - Added BUILD_JNI, BUILD_PERL_NATIVES, BUILD_PERL_INTERPRETER and JVM_LIB_TYPE build + configuration options to help with automated builds + - Added BIND configuration option. See docs for details. + Note: The Java JVM server noe listens on localhost by default (instead of 0.0.0.0). + - Other minor bug fixes + +0.50 Mon Jan 31 20:14:43 EST 2005 + - Added HOST configuration option to access JVM server remotely + - Fixed bug with paths in Cygwin + - Fixed bug with regexp that was badly interpreted under the debugger + - Extended InlineJavaClassLoader from current thread ClassLoader to enable it to work + properly under Tomcat + - Include patch by Ihab Awad that allows running the JVM server inside Tomcat + - Fixed bug with longs over 32 bits in length and loosened boundary checking in numeric values + since it was not portable + - Fixed bug (found by Dean Thayer) with socket not closed properly in Java + - Other minor bug fixes + +0.49 + - Added PerlInterpreter: require/eval Perl code directly from Java + - Reworked type casting: changes are NOT backwards compatible :( + - Callbacks can now be called in list context using "@function_name" + - Renamed PerlNatives stuff + - Added external command line tool to start/stop a SHARED_JVM server + - Applied JNI memory leak patch by Jeff Janes + - Removed exports from Inline::Java::Portable + - Split and updated documentation + +0.47 Sat Feb 14 10:00:00 EST 2004 + - Fixed bugs in portability code and added HPUX, AIX and Solaris specifics + - Tweaked CLASSPATH directory order + - Changed port numbers for SHARED_JVM tests in order to avoid clashes + with running installations + - Made PerlNatives optional + +0.46 Web Feb 04 20:00:00 EST 2004 + - Fixed Natives.xs to work with ExtUtils::ParseXS + +0.45 Fri Jan 30 20:00:00 EST 2004 + - Fixed Makefile.PL arguments that were getting lost + - Fixed deprecated require call + - Fixed support for symbolic links in J2SDK directory + - Basic support for J2SDK 1.5 + - Added new experimental feature: PerlNatives + +0.44 Sun Nov 23 15:47:06 EST 2003 + - Callbacks from multiple threads are now supported + - Refactored (again...) studying/.jdat/cache stuff + - Added PRIVATE mode for use with SHARED_JVM + - Added DEBUGGER mode that launches jdb + - Fixed memory leak in JNI code (patch submitted by Dave Blob) + +0.43 Tue Oct 14 13:18:25 EDT 2003 + - Restored $VERSION in each .pm file + - Inline::Java now formerly requires Perl 5.6 + +0.42 Fri Sep 5 13:18:25 EDT 2003 + - Fixed more CLASSPATH issues. CLASSPATH now works like this: + * CLASSPATH environment variable is global + * CLASSPATH configuration option is local to the user class loader + - Added method cache to increase performance and decrease reflection + API calls + - Altered and documented the study_classes() function + - Added EXTRA_JAVA_ARGS and EXTRA_JAVAC_ARGS config options + +0.41 Mon Jul 14 13:18:25 EDT 2003 + - Fixed CLASSPATH bug + - Possibly (!) fixed test suite problems under heavy load + +0.40 Fri Apr 11 11:00:00 EST 2003 + - Patch by Doug MacEachern to allow running under servlet engines. + - Patch by John Kinsley for handling empty arrays. + - Custom ClassLoader allows for better isolation of user code and + dynamic 'CLASSPATH' (multiple sections/clients are now totally + independant). + - Core Java code is now compiled separately (at install time) instead + of with the user code (for each script). This dramatically improves + build time. + - Default port number changed to 0 (next available port number). + This allows many multiple users to run concurrently without + clashes. + Note: The default port on systems where this feature is not + available has remained 7890. + Note: The default port for SHARED_JVM mode is now 7891. + - SHARED_JVM mode now calls release_JVM() automatically. + - UTF8 support + - User code can now be a public class and (optionally) inside a package. + - Callback classes have changed. + Note: These classes are now in the org.perl.inline.java package. + Note: PerlException has been renamed InlineJavaPerlException. + Note: If you presently use callbacks, your code may now no longer compile. + You will need to apply the changes listed above to fix it. + - study_classes now returns the package in which the studied classes + have been put. + - Many bug fixes and documentation corrections. + +0.33 Mon Jun 17 13:50:14 EDT 2002 + - Improved and more flexible debug output + - Auto loading of thread libraries under Solaris + - Basic support for J2SDK 1.4 + - Other minor bug fixes + +0.32 Sat Apr 6 11:45:06 EST 2002 + - Restored compatibility with perl 5.005_03 + - Other minor bug fixes + +0.31 Mon Feb 4 15:45:06 EDT 2002 + - Exception handling (Perl can 'catch' Java exceptions) + - Callbacks to Perl from Java + - More complete test suite + - Improved documentation and related examples + - Improved installation script and directions + - Other minor bug fixes + +0.30 Mon Sep 17 15:45:06 EDT 2001 + - JVM server is now multi-threaded + - Added 'SHARED_JVM' option for mod_perl support + - Beefed up test suite + +0.23 Thu Aug 30 08:41:11 EDT 2001 + - Added support for multiple Inline sections using a special notation + in the CLASSPATH. + - Added the 'fix' Makefile.PL option au automatically fix the Makefile + for Win95/98/Me. + +0.22 Fri Jun 1 13:31:35 EDT 2001 + - Fixed up Inline::Java to work with Inline 0.41 + - Added support for java.lang.Number as a primitive numeric type. Any + Perl scalar passed as a java.lang.Number will instantiate + a java.lang.Double on the Java side. + +0.21 Tue May 8 11:32:28 EDT 2001 + - Added 'studying' of external Java classes + - Added support for default no-arg constructors for public classes + - Caching for class information + - Added error message stating that Inline::Java doesn't currently + support multiple sections + +0.20 Sat Apr 14 23:00:00 EDT 2001 + - Added optional JNI extension. + - Added support for arrays. + - Added support for public member variables. + - Added support for public static member variables. + - Added type casting. + +0.01 Thu Feb 15 14:01:25 EST 2001 + - Created Inline::Java. + @@ -0,0 +1,1191 @@ +package Inline::Java ; +@Inline::Java::ISA = qw(Inline Exporter) ; + +# Export the cast function if wanted +@EXPORT_OK = qw(cast coerce study_classes caught jar j2sdk) ; + + +use strict ; +require 5.006 ; + +$Inline::Java::VERSION = '0.53' ; + + +# DEBUG is set via the DEBUG config +if (! defined($Inline::Java::DEBUG)){ + $Inline::Java::DEBUG = 0 ; +} + +# Set DEBUG stream +*DEBUG_STREAM = *STDERR ; + +require Inline ; +use Carp ; +use Config ; +use File::Copy ; +use File::Spec ; +use Cwd ; +use Data::Dumper ; + +use Inline::Java::Portable ; +use Inline::Java::Class ; +use Inline::Java::Object ; +use Inline::Java::Array ; +use Inline::Java::Handle ; +use Inline::Java::Protocol ; +use Inline::Java::Callback ; +# Must be last. +use Inline::Java::JVM ; +# Our default J2SK +require Inline::Java->find_default_j2sdk() ; + + +# This is set when the script is over. +my $DONE = 0 ; + +# This is set when at least one JVM is loaded. +my $JVM = undef ; + +# This list will store the $o objects... +my @INLINES = () ; + +my $report_version = "V2" ; + +# This stuff is to control the termination of the Java Interpreter +sub done { + my $signal = shift ; + + # To preserve the passed exit code... + my $ec = $? ; + + $DONE = 1 ; + + if (! $signal){ + Inline::Java::debug(1, "killed by natural death.") ; + } + else{ + Inline::Java::debug(1, "killed by signal SIG$signal.") ; + } + + shutdown_JVM() ; + Inline::Java::debug(1, "exiting with $ec") ; + CORE::exit($ec) ; + exit($ec) ; +} + + +END { + if ($DONE < 1){ + done() ; + } +} + + +# To export the cast function and others. +sub import { + my $class = shift ; + + foreach my $a (@_){ + if ($a eq 'jar'){ + print Inline::Java::Portable::get_server_jar() ; + exit() ; + } + elsif ($a eq 'j2sdk'){ + print Inline::Java->find_default_j2sdk() . " says '" . + Inline::Java::get_default_j2sdk() . "'\n" ; + exit() ; + } + elsif ($a eq 'so_dirs'){ + print Inline::Java::Portable::portable('SO_LIB_PATH_VAR') . "=" . + join(Inline::Java::Portable::portable('ENV_VAR_PATH_SEP'), + Inline::Java::get_default_j2sdk_so_dirs()) ; + exit() ; + } + } + $class->export_to_level(1, $class, @_) ; +} + + + +######################## Inline interface ######################## + + + +# Register this module as an Inline language support module +sub register { + return { + language => 'Java', + aliases => ['JAVA', 'java'], + type => 'interpreted', + suffix => 'jdat', + } ; +} + + +# Here validate is overridden because some of the config options are needed +# at load as well. +sub validate { + my $o = shift ; + + # This might not print since debug is set further down... + Inline::Java::debug(1, "Starting validate.") ; + + my $jdk = Inline::Java::get_default_j2sdk() ; + my $dbg = $Inline::Java::DEBUG ; + my %opts = @_ ; + $o->set_option('DEBUG', $dbg, 'i', 1, \%opts) ; + $o->set_option('J2SDK', $jdk, 's', 1, \%opts) ; + $o->set_option('CLASSPATH', '', 's', 1, \%opts) ; + + $o->set_option('BIND', 'localhost', 's', 1, \%opts) ; + $o->set_option('HOST', 'localhost', 's', 1, \%opts) ; + $o->set_option('PORT', -1, 'i', 1, \%opts) ; + $o->set_option('STARTUP_DELAY', 15, 'i', 1, \%opts) ; + $o->set_option('SHARED_JVM', 0, 'b', 1, \%opts) ; + $o->set_option('START_JVM', 1, 'b', 1, \%opts) ; + $o->set_option('JNI', 0, 'b', 1, \%opts) ; + $o->set_option('EMBEDDED_JNI', 0, 'b', 1, \%opts) ; + $o->set_option('NATIVE_DOUBLES', 0, 'b', 1, \%opts) ; + + $o->set_option('WARN_METHOD_SELECT', 0, 'b', 1, \%opts) ; + $o->set_option('STUDY', undef, 'a', 0, \%opts) ; + $o->set_option('AUTOSTUDY', 0, 'b', 1, \%opts) ; + + $o->set_option('EXTRA_JAVA_ARGS', '', 's', 1, \%opts) ; + $o->set_option('EXTRA_JAVAC_ARGS', '', 's', 1, \%opts) ; + $o->set_option('DEBUGGER', 0, 'b', 1, \%opts) ; + + $o->set_option('PRIVATE', '', 'b', 1, \%opts) ; + $o->set_option('PACKAGE', '', 's', 1, \%opts) ; + + my @left_overs = keys(%opts) ; + if (scalar(@left_overs)){ + croak "'$left_overs[0]' is not a valid configuration option for Inline::Java" ; + } + + # Now for the post processing + $Inline::Java::DEBUG = $o->get_java_config('DEBUG') ; + + # Embedded JNI turns on regular JNI + if ($o->get_java_config('EMBEDDED_JNI')){ + $o->set_java_config('JNI', 1) ; + } + + if ($o->get_java_config('PORT') == -1){ + if ($o->get_java_config('SHARED_JVM')){ + $o->set_java_config('PORT', 7891) ; + } + else{ + $o->set_java_config('PORT', -7890) ; + } + } + + if (($o->get_java_config('JNI'))&&($o->get_java_config('SHARED_JVM'))){ + croak("You can't use the 'SHARED_JVM' option in 'JNI' mode") ; + } + if (($o->get_java_config('JNI'))&&($o->get_java_config('DEBUGGER'))){ + croak("You can't invoke the Java debugger ('DEBUGGER' option) in 'JNI' mode") ; + } + if ((! $o->get_java_config('SHARED_JVM'))&&(! $o->get_java_config('START_JVM'))){ + croak("Disabling the 'START_JVM' option only makes sense in 'SHARED_JVM' mode") ; + } + + if ($o->get_java_config('JNI')){ + require Inline::Java::JNI ; + } + + if ($o->get_java_config('DEBUGGER')){ + # Here we want to tweak a few settings to help debugging... + Inline::Java::debug(1, "Debugger mode activated") ; + # Add the -g compile option + $o->set_java_config('EXTRA_JAVAC_ARGS', $o->get_java_config('EXTRA_JAVAC_ARGS') . " -g ") ; + # Add the -sourcepath runtime option + $o->set_java_config('EXTRA_JAVA_ARGS', $o->get_java_config('EXTRA_JAVA_ARGS') . + " -sourcepath " . $o->get_api('build_dir') . + Inline::Java::Portable::portable("ENV_VAR_PATH_SEP_CP") . + Inline::Java::Portable::get_source_dir() + ) ; + } + + my $study = $o->get_java_config('STUDY') ; + if ((defined($study))&&(ref($study) ne 'ARRAY')){ + croak "Configuration option 'STUDY' must be an array of Java class names" ; + } + + Inline::Java::debug(1, "validate done.") ; +} + + +sub set_option { + my $o = shift ; + my $name = shift ; + my $default = shift ; + my $type = shift ; + my $env_or = shift ; + my $opts = shift ; + my $desc = shift ; + + if (! exists($o->{ILSM}->{$name})){ + my $val = undef ; + if (($env_or)&&(exists($ENV{"PERL_INLINE_JAVA_$name"}))){ + $val = $ENV{"PERL_INLINE_JAVA_$name"} ; + } + elsif (exists($opts->{$name})){ + $val = $opts->{$name} ; + } + else{ + $val = $default ; + } + + if ($type eq 'b'){ + if (! defined($val)){ + $val = 0 ; + } + $val = ($val ? 1 : 0) ; + } + elsif ($type eq 'i'){ + if ((! defined($val))||($val !~ /\d/)){ + $val = 0 ; + } + $val = int($val) ; + } + + $o->set_java_config($name, $val) ; + } + + delete $opts->{$name} ; +} + + +sub get_java_config { + my $o = shift ; + my $param = shift ; + + return $o->{ILSM}->{$param} ; +} + + +sub set_java_config { + my $o = shift ; + my $param = shift ; + my $value = shift ; + + return $o->{ILSM}->{$param} = $value ; +} + + +# In theory we shouldn't need to use this, but it seems +# it's not all accessible by the API yet. +sub get_config { + my $o = shift ; + my $param = shift ; + + return $o->{CONFIG}->{$param} ; +} + + +sub get_api { + my $o = shift ; + my $param = shift ; + + # Allows us to force a specific package... + if (($param eq 'pkg')&&($o->get_config('PACKAGE'))){ + return $o->get_config('PACKAGE') ; + } + + return $o->{API}->{$param} ; +} + + +# Parse and compile Java code +sub build { + my $o = shift ; + + if ($o->get_java_config('built')){ + return ; + } + + Inline::Java::debug(1, "Starting build.") ; + + # Grab and untaint the current directory + my $cwd = Cwd::cwd() ; + if ($o->get_config('UNTAINT')){ + ($cwd) = $cwd =~ /(.*)/ ; + } + + # We must grab this before we change to the build dir because + # it could be relative... + my $server_jar = Inline::Java::Portable::get_server_jar() ; + + # We need to add all the previous install dirs to the classpath because + # they can access each other. + my @prev_install_dirs = () ; + foreach my $in (@INLINES){ + push @prev_install_dirs, File::Spec->catdir($in->get_api('install_lib'), + 'auto', $in->get_api('modpname')) ; + } + + my $cp = $ENV{CLASSPATH} || '' ; + $ENV{CLASSPATH} = Inline::Java::Portable::make_classpath($server_jar, @prev_install_dirs, $o->get_java_config('CLASSPATH')) ; + Inline::Java::debug(2, "classpath: $ENV{CLASSPATH}") ; + + # Create the build dir and go there + my $build_dir = $o->get_api('build_dir') ; + $o->mkpath($build_dir) ; + chdir $build_dir ; + + my $code = $o->get_api('code') ; + my $pcode = $code ; + my $study_only = ($code =~ /^(STUDY|SERVER)$/) ; + my $source = ($study_only ? '' : $o->get_api('modfname') . ".java") ; + + # Parse code to check for public class + $pcode =~ s/\\\"//g ; + $pcode =~ s/\"(.*?)\"//g ; + $pcode =~ s/\/\*(.*?)\*\///gs ; + $pcode =~ s/\/\/(.*)$//gm ; + if ($pcode =~ /public\s+(abstract\s+)?class\s+(\w+)/){ + $source = "$2.java" ; + } + + my $install_dir = File::Spec->catdir($o->get_api('install_lib'), + 'auto', $o->get_api('modpname')) ; + $o->mkpath($install_dir) ; + + if ($source){ + # Dump the source code... + open(Inline::Java::JAVA, ">$source") or + croak "Can't open $source: $!" ; + print Inline::Java::JAVA $code ; + close(Inline::Java::JAVA) ; + + # ... and compile it. + my $javac = File::Spec->catfile($o->get_java_config('J2SDK'), + Inline::Java::Portable::portable("J2SDK_BIN"), + "javac" . Inline::Java::Portable::portable("EXE_EXTENSION")) ; + my $redir = Inline::Java::Portable::portable("IO_REDIR") ; + + my $args = "-deprecation " . $o->get_java_config('EXTRA_JAVAC_ARGS') ; + my $pinstall_dir = Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", $install_dir) ; + my $cmd = Inline::Java::Portable::portable("SUB_FIX_CMD_QUOTES", + "\"$javac\" $args -d \"$pinstall_dir\" $source > cmd.out $redir") ; + if ($o->get_config('UNTAINT')){ + ($cmd) = $cmd =~ /(.*)/ ; + } + Inline::Java::debug(2, "$cmd") ; + my $res = system($cmd) ; + my $msg = $o->get_compile_error_msg() ; + if ($res){ + croak $o->compile_error_msg($cmd, $msg) ; + } ; + if ($msg){ + warn("\n$msg\n") ; + } + + # When we run the commands, we quote them because in WIN32 you need it if + # the programs are in directories which contain spaces. Unfortunately, in + # WIN9x, when you quote a command, it masks it's exit value, and 0 is always + # returned. Therefore a command failure is not detected. + # We need to take care of checking whether there are actually files + # to be copied, and if not will exit the script. + if (Inline::Java::Portable::portable('COMMAND_COM')){ + my @fl = Inline::Java::Portable::find_classes_in_dir($install_dir) ; + if (! scalar(@fl)){ + croak "No class files produced. Previous command failed under command.com?" ; + } + foreach my $f (@fl){ + if (! (-s $f->{file})){ + croak "File $f->{file} has size zero. Previous command failed under command.com?" ; + } + } + } + } + + $ENV{CLASSPATH} = $cp ; + Inline::Java::debug(2, "classpath: $ENV{CLASSPATH}") ; + + # Touch the .jdat file. + my $jdat = File::Spec->catfile($install_dir, $o->get_api('modfname') . '.' . $o->get_api('suffix')) ; + if (! open(Inline::Java::TOUCH, ">$jdat")){ + croak "Can't create file $jdat" ; + } + close(Inline::Java::TOUCH) ; + + # Go back and clean up + chdir $cwd ; + if (($o->get_api('cleanup'))&&(! $o->get_java_config('DEBUGGER'))){ + $o->rmpath('', $build_dir) ; + } + + $o->set_java_config('built', 1) ; + Inline::Java::debug(1, "build done.") ; +} + + +sub get_compile_error_msg { + my $o = shift ; + + my $msg = '' ; + if (open(Inline::Java::CMD, "<cmd.out")){ + $msg = join("", <Inline::Java::CMD>) ; + close(Inline::Java::CMD) ; + } + + return $msg ; +} + + +sub compile_error_msg { + my $o = shift ; + my $cmd = shift ; + my $error = shift ; + + my $build_dir = $o->get_api('build_dir') ; + + my $lang = $o->get_api('language') ; + return <<MSG + +A problem was encountered while attempting to compile and install your Inline +$lang code. The command that failed was: + $cmd + +The build directory was: +$build_dir + +The error message was: +$error + +To debug the problem, cd to the build directory, and inspect the output files. + +MSG +; +} + + +# Load and Run the Java Code. +sub load { + my $o = shift ; + + if ($o->get_java_config('loaded')){ + return ; + } + + Inline::Java::debug(1, "Starting load.") ; + + my $install_dir = File::Spec->catdir($o->get_api('install_lib'), + 'auto', $o->get_api('modpname')) ; + + # If the JVM is not running, we need to start it here. + my $cp = $ENV{CLASSPATH} || '' ; + if (! $JVM){ + $ENV{CLASSPATH} = Inline::Java::Portable::make_classpath( + Inline::Java::Portable::get_server_jar()) ; + Inline::Java::debug(2, "classpath: $ENV{CLASSPATH}") ; + $JVM = new Inline::Java::JVM($o) ; + $ENV{CLASSPATH} = $cp ; + Inline::Java::debug(2, "classpath: $ENV{CLASSPATH}") ; + + my $pc = new Inline::Java::Protocol(undef, $o) ; + $pc->AddClassPath(Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", Inline::Java::Portable::get_user_jar())) ; + + my $st = $pc->ServerType() ; + if ((($st eq "shared")&&(! $o->get_java_config('SHARED_JVM')))|| + (($st eq "private")&&($o->get_java_config('SHARED_JVM')))){ + croak "JVM type mismatch on port " . $JVM->{port} ; + } + } + + $ENV{CLASSPATH} = '' ; + my @cp = Inline::Java::Portable::make_classpath($install_dir, $o->get_java_config('CLASSPATH')) ; + $ENV{CLASSPATH} = $cp ; + + my $pc = new Inline::Java::Protocol(undef, $o) ; + $pc->AddClassPath(@cp) ; + + # Add our Inline object to the list. + push @INLINES, $o ; + $o->set_java_config('id', scalar(@INLINES) - 1) ; + Inline::Java::debug(3, "Inline::Java object id is " . $o->get_java_config('id')) ; + + $o->study_module() ; + if ((defined($o->get_java_config('STUDY')))&&(scalar($o->get_java_config('STUDY')))){ + $o->_study($o->get_java_config('STUDY')) ; + } + + $o->set_java_config('loaded', 1) ; + Inline::Java::debug(1, "load done.") ; +} + + +# This function 'studies' the classes generated by the inlined code. +sub study_module { + my $o = shift ; + + my $install_dir = File::Spec->catdir($o->get_api('install_lib'), + 'auto', $o->get_api('modpname')) ; + my $cache = $o->get_api('modfname') . '.' . $o->get_api('suffix') ; + + my $lines = [] ; + if (! $o->get_java_config('built')){ + # Since we didn't build the module, this means that + # it was up to date. We can therefore use the data + # from the cache. + Inline::Java::debug(1, "using jdat cache") ; + my $p = File::Spec->catfile($install_dir, $cache) ; + my $size = (-s $p) || 0 ; + if ($size > 0){ + if (open(Inline::Java::CACHE, "<$p")){ + while (<Inline::Java::CACHE>){ + push @{$lines}, $_ ; + } + close(Inline::Java::CACHE) ; + } + else{ + croak "Can't open $p for reading: $!" ; + } + } + } + else{ + # First thing to do is get the list of classes that comprise the module. + + # We need the classes that are in the directory or under... + my @classes = () ; + my $cwd = Cwd::cwd() ; + if ($o->get_config('UNTAINT')){ + ($cwd) = $cwd =~ /(.*)/ ; + } + + # We chdir to the install dir, that makes it easier to figure out + # the packages for the classes. + chdir($install_dir) ; + my @fl = Inline::Java::Portable::find_classes_in_dir('.') ; + chdir $cwd ; + foreach my $f (@fl){ + push @classes, $f->{class} ; + } + + # Now we ask Java the info about those classes... + $lines = $o->report(@classes) ; + + # and we update the cache with these results. + Inline::Java::debug(1, "updating jdat cache") ; + my $p = File::Spec->catfile($install_dir, $cache) ; + if (open(Inline::Java::CACHE, ">$p")){ + foreach my $l (@{$lines}){ + print Inline::Java::CACHE "$l\n" ; + } + close(Inline::Java::CACHE) ; + } + else{ + croak "Can't open $p file for writing" ; + } + } + + # Now we read up the symbols and bind them to Perl. + $o->bind_jdat($o->load_jdat($lines)) ; +} + + +# This function 'studies' the specified classes and binds them to +# Perl. +sub _study { + my $o = shift ; + my $classes = shift ; + + my @new_classes = () ; + foreach my $class (@{$classes}){ + $class = Inline::Java::Class::ValidateClass($class) ; + if (! Inline::Java::known_to_perl($o->get_api('pkg'), $class)){ + push @new_classes, $class ; + } + } + if (! scalar(@new_classes)){ + return ; + } + + my $lines = $o->report(@new_classes) ; + # Now we read up the symbols and bind them to Perl. + $o->bind_jdat($o->load_jdat($lines)) ; +} + + +sub report { + my $o = shift ; + my @classes = @_ ; + + my @lines = () ; + if (scalar(@classes)){ + my $pc = new Inline::Java::Protocol(undef, $o) ; + my $resp = $pc->Report(join(" ", @classes)) ; + @lines = split("\n", $resp) ; + } + + return \@lines ; +} + + +# Load the jdat code information file. +sub load_jdat { + my $o = shift ; + my $lines = shift ; + + Inline::Java::debug_obj($lines) ; + + # We need an array here since the same object can have many + # study sessions. + if (! defined($o->{ILSM}->{data})){ + $o->{ILSM}->{data} = [] ; + } + my $d = {} ; + my $data_idx = scalar(@{$o->{ILSM}->{data}}) ; + push @{$o->{ILSM}->{data}}, $d ; + + # The original regexp didn't match anymore under the debugger... + # Very strange indeed... + # my $re = '[\w.\$\[;]+' ; + my $re = '.+' ; + + my $idx = 0 ; + my $current_class = undef ; + if (scalar(@{$lines})){ + my $vline = shift @{$lines} ; + chomp($vline) ; + if ($vline ne $report_version){ + croak("Report version mismatch ($vline != $report_version). Delete your '_Inline' and try again.") ; + } + } + foreach my $line (@{$lines}){ + chomp($line) ; + if ($line =~ /^class ($re) ($re)$/){ + # We found a class definition + my $java_class = $1 ; + my $parent_java_class = $2 ; + $current_class = Inline::Java::java2perl($o->get_api('pkg'), $java_class) ; + $d->{classes}->{$current_class} = {} ; + $d->{classes}->{$current_class}->{java_class} = $java_class ; + if ($parent_java_class ne "null"){ + $d->{classes}->{$current_class}->{parent_java_class} = $parent_java_class ; + } + $d->{classes}->{$current_class}->{constructors} = {} ; + $d->{classes}->{$current_class}->{methods} = {} ; + $d->{classes}->{$current_class}->{fields} = {} ; + } + elsif ($line =~ /^constructor \((.*)\)$/){ + my $signature = $1 ; + + $d->{classes}->{$current_class}->{constructors}->{$signature} = + { + SIGNATURE => [split(", ", $signature)], + STATIC => 1, + IDX => $idx, + } ; + } + elsif ($line =~ /^method (\w+) ($re) (\w+)\((.*)\)$/){ + my $static = $1 ; + my $declared_in = $2 ; + my $method = $3 ; + my $signature = $4 ; + + if (! defined($d->{classes}->{$current_class}->{methods}->{$method})){ + $d->{classes}->{$current_class}->{methods}->{$method} = {} ; + } + + $d->{classes}->{$current_class}->{methods}->{$method}->{$signature} = + { + SIGNATURE => [split(", ", $signature)], + STATIC => ($static eq "static" ? 1 : 0), + IDX => $idx, + } ; + } + elsif ($line =~ /^field (\w+) ($re) (\w+) ($re)$/){ + my $static = $1 ; + my $declared_in = $2 ; + my $field = $3 ; + my $type = $4 ; + + if (! defined($d->{classes}->{$current_class}->{fields}->{$field})){ + $d->{classes}->{$current_class}->{fields}->{$field} = {} ; + } + + $d->{classes}->{$current_class}->{fields}->{$field}->{$type} = + { + TYPE => $type, + STATIC => ($static eq "static" ? 1 : 0), + IDX => $idx, + } ; + } + $idx++ ; + } + + Inline::Java::debug_obj($d) ; + + return ($d, $data_idx) ; +} + + +# Binds the classes and the methods to Perl +sub bind_jdat { + my $o = shift ; + my $d = shift ; + my $idx = shift ; + + if (! defined($d->{classes})){ + return ; + } + + my $inline_idx = $o->get_java_config('id') ; + + my %classes = %{$d->{classes}} ; + foreach my $class (sort keys %classes) { + my $class_name = $class ; + $class_name =~ s/^(.*)::// ; + + my $java_class = $d->{classes}->{$class}->{java_class} ; + # This parent stuff is needed for PerlNatives (so that you can call PerlNatives methods + # from Perl...) + my $parent_java_class = $d->{classes}->{$class}->{parent_java_class} ; + my $parent_module = '' ; + my $parent_module_declare = '' ; + if (defined($parent_java_class)){ + $parent_module = java2perl($o->get_api('pkg'), $parent_java_class) ; + $parent_module_declare = "\$$parent_module" . "::EXISTS_AS_PARENT = 1 ;" ; + $parent_module .= ' ' ; + } + if (Inline::Java::known_to_perl($o->get_api('pkg'), $java_class)){ + next ; + } + + my $colon = ":" ; + my $dash = "-" ; + my $ijo = 'Inline::Java::Object' ; + + my $code = <<CODE; +package $class ; +use vars qw(\@ISA \$INLINE \$EXISTS \$JAVA_CLASS \$DUMMY_OBJECT) ; + +$parent_module_declare +\@ISA = qw($parent_module$ijo) ; +\$INLINE = \$INLINES[$inline_idx] ; +\$EXISTS = 1 ; +\$JAVA_CLASS = '$java_class' ; +\$DUMMY_OBJECT = $class$dash>__new( + \$JAVA_CLASS, \$INLINE, 0) ; + +use Carp ; + +CODE + + while (my ($field, $types) = each %{$d->{classes}->{$class}->{fields}}){ + while (my ($type, $sign) = each %{$types}){ + if ($sign->{STATIC}){ + $code .= <<CODE; +tie \$$class$colon:$field, "Inline::Java::Object::StaticMember", + \$DUMMY_OBJECT, + '$field' ; +CODE + # We have at least one static version of this field, + # that's enough. + # Don't forget to reset the 'each' static pointer + keys %{$types} ; + last ; + } + } + } + + + if (scalar(keys %{$d->{classes}->{$class}->{constructors}})){ + $code .= <<CODE; + +sub new { + my \$class = shift ; + my \@args = \@_ ; + + my \$o = \$INLINE ; + my \$d = \$o->{ILSM}->{data}->[$idx] ; + my \$signatures = \$d->{classes}->{'$class'}->{constructors} ; + my (\$proto, \$new_args, \$static) = \$class->__validate_prototype('new', [\@args], \$signatures, \$o) ; + + my \$ret = undef ; + eval { + \$ret = \$class->__new(\$JAVA_CLASS, \$o, -1, \$proto, \$new_args) ; + } ; + croak \$@ if \$@ ; + + return \$ret ; +} + + +sub $class_name { + return new(\@_) ; +} + +CODE + } + + while (my ($method, $sign) = each %{$d->{classes}->{$class}->{methods}}){ + $code .= $o->bind_method($idx, $class, $method) ; + } + + Inline::Java::debug_obj(\$code) ; + + # open (Inline::Java::CODE, ">>code") and print CODE $code and close(CODE) ; + + # Here it seems that for the eval below to resolve the @INLINES + # list properly, it must be used in this function... + my $dummy = scalar(@INLINES) ; + + eval $code ; + + croak $@ if $@ ; + } +} + + +sub bind_method { + my $o = shift ; + my $idx = shift ; + my $class = shift ; + my $method = shift ; + my $static = shift ; + + my $code = <<CODE; + +sub $method { + my \$this = shift ; + my \@args = \@_ ; + + my \$o = \$INLINE ; + my \$d = \$o->{ILSM}->{data}->[$idx] ; + my \$signatures = \$d->{classes}->{'$class'}->{methods}->{'$method'} ; + my (\$proto, \$new_args, \$static) = \$this->__validate_prototype('$method', [\@args], \$signatures, \$o) ; + + if ((\$static)&&(! ref(\$this))){ + \$this = \$DUMMY_OBJECT ; + } + + my \$ret = undef ; + eval { + \$ret = \$this->__get_private()->{proto}->CallJavaMethod('$method', \$proto, \$new_args) ; + } ; + croak \$@ if \$@ ; + + return \$ret ; +} + +CODE + + return $code ; +} + + +sub get_fields { + my $o = shift ; + my $class = shift ; + + my $fields = {} ; + my $data_list = $o->{ILSM}->{data} ; + + foreach my $d (@{$data_list}){ + if (exists($d->{classes}->{$class})){ + while (my ($field, $value) = each %{$d->{classes}->{$class}->{fields}}){ + # Here $value is a hash that contains all the different + # types available for the field $field + $fields->{$field} = $value ; + } + } + } + + return $fields ; +} + + +# Return a small report about the Java code. +sub info { + my $o = shift ; + + if (! (($o->{INLINE}->{object_ready})||($o->get_java_config('built')))){ + $o->build() ; + } + + if (! $o->get_java_config('loaded')){ + $o->load() ; + } + + my $info = '' ; + my $data_list = $o->{ILSM}->{data} ; + + foreach my $d (@{$data_list}){ + if (! defined($d->{classes})){ + next ; + } + + my %classes = %{$d->{classes}} ; + + $info .= "The following Java classes have been bound to Perl:\n" ; + foreach my $class (sort keys %classes) { + $info .= "\n class $class:\n" ; + + $info .= " public methods:\n" ; + while (my ($k, $v) = each %{$d->{classes}->{$class}->{constructors}}){ + my $name = $class ; + $name =~ s/^(.*)::// ; + $info .= " $name($k)\n" ; + } + + while (my ($k, $v) = each %{$d->{classes}->{$class}->{methods}}){ + while (my ($k2, $v2) = each %{$d->{classes}->{$class}->{methods}->{$k}}){ + my $static = ($v2->{STATIC} ? "static " : "") ; + $info .= " $static$k($k2)\n" ; + } + } + + $info .= " public member variables:\n" ; + while (my ($k, $v) = each %{$d->{classes}->{$class}->{fields}}){ + while (my ($k2, $v2) = each %{$d->{classes}->{$class}->{fields}->{$k}}){ + my $static = ($v2->{STATIC} ? "static " : "") ; + my $type = $v2->{TYPE} ; + + $info .= " $static$type $k\n" ; + } + } + } + } + + return $info ; +} + + + +######################## General Functions ######################## + + +sub __get_JVM { + return $JVM ; +} + + +# For testing purposes only... +sub __clear_JVM { + $JVM = undef ; +} + + +sub shutdown_JVM { + if ($JVM){ + $JVM->shutdown() ; + $JVM = undef ; + } +} + + +sub reconnect_JVM { + if ($JVM){ + $JVM->reconnect() ; + } +} + + +sub capture_JVM { + if ($JVM){ + $JVM->capture() ; + } +} + + +sub i_am_JVM_owner { + if ($JVM){ + return $JVM->am_owner() ; + } +} + + +sub release_JVM { + if ($JVM){ + $JVM->release() ; + } +} + + +sub get_DEBUG { + return $Inline::Java::DEBUG ; +} + + +sub get_DONE { + return $DONE ; +} + + +sub set_DONE { + $DONE = 1 ; +} + + +sub __get_INLINES { + return \@INLINES ; +} + + +sub java2perl { + my $pkg = shift ; + my $jclass = shift ; + + $jclass =~ s/[.\$]/::/g ; + + if ((defined($pkg))&&($pkg)){ + $jclass = $pkg . "::" . $jclass ; + } + + return $jclass ; +} + + +sub known_to_perl { + my $pkg = shift ; + my $jclass = shift ; + + my $perl_class = java2perl($pkg, $jclass) ; + + no strict 'refs' ; + if (defined(${$perl_class . "::" . "EXISTS"})){ + Inline::Java::debug(3, "perl knows about '$jclass' ('$perl_class')") ; + return 1 ; + } + else{ + Inline::Java::debug(3, "perl doesn't know about '$jclass' ('$perl_class')") ; + } + + return 0 ; +} + + +sub debug { + my $level = shift ; + + if (($Inline::Java::DEBUG)&&($Inline::Java::DEBUG >= $level)){ + my $x = " " x $level ; + my $str = join("\n$x", @_) ; + while (chomp($str)) {} + print DEBUG_STREAM sprintf("[perl][%s]$x%s\n", $level, $str) ; + } +} + + +sub debug_obj { + my $obj = shift ; + my $force = shift || 0 ; + + if (($Inline::Java::DEBUG >= 5)||($force)){ + debug(5, "Dump:\n" . Dumper($obj)) ; + if (UNIVERSAL::isa($obj, "Inline::Java::Object")){ + # Print the guts as well... + debug(5, "Private Dump:" . Dumper($obj->__get_private())) ; + } + } +} + + +sub dump_obj { + my $obj = shift ; + + return debug_obj($obj, 1) ; +} + + +######################## Public Functions ######################## + + +# If we are dealing with a Java object, we simply ask for a new "reference" +# with the requested class. +sub cast { + my $type = shift ; + my $val = shift ; + + if (! UNIVERSAL::isa($val, "Inline::Java::Object")){ + croak("Type casting can only be used on Java objects. Use 'coerce' instead.") ; + } + + return $val->__cast($type) ; +} + + +# coerce is used to force a specific prototype to be used. +sub coerce { + my $type = shift ; + my $val = shift ; + my $array_type = shift ; + + if (UNIVERSAL::isa($val, "Inline::Java::Object")){ + croak("Type coercing can't be used on Java objects. Use 'cast' instead.") ; + } + + my $o = undef ; + eval { + $o = new Inline::Java::Class::Coerce($type, $val, $array_type) ; + } ; + croak $@ if $@ ; + + return $o ; +} + + +sub study_classes { + my $classes = shift ; + my $package = shift || caller() ; + + my $o = undef ; + my %pkgs = () ; + foreach (@INLINES){ + my $i = $_ ; + my $pkg = $i->get_api('pkg') || 'main' ; + $pkgs{$pkg} = 1 ; + if ($pkg eq $package){ + $o = $i ; + last ; + } + } + + if (defined($o)){ + $o->_study($classes) ; + } + else { + my $msg = "Can't place studied classes under package '$package' since Inline::Java was not used there. Valid packages are:\n" ; + foreach my $pkg (keys %pkgs){ + $msg .= " $pkg\n" ; + } + croak($msg) ; + } +} + + +sub caught { + my $class = shift ; + + my $e = $@ ; + + $class = Inline::Java::Class::ValidateClass($class) ; + + my $ret = 0 ; + if (($e)&&(UNIVERSAL::isa($e, "Inline::Java::Object"))){ + my ($msg, $score) = $e->__isa($class) ; + if ($msg){ + $ret = 0 ; + } + else{ + $ret = 1 ; + } + } + $@ = $e ; + + return $ret ; +} + + +sub find_default_j2sdk { + my $class = shift ; + + return File::Spec->catfile('Inline', 'Java', 'default_j2sdk.pl') ; +} + + +1 ; diff --git a/Java.pod b/Java.pod new file mode 100644 index 0000000..453a868 --- /dev/null +++ b/Java.pod @@ -0,0 +1,1162 @@ +=head1 NAME + +Inline::Java - Write Perl classes in Java. + +=head1 SYNOPSIS + +=for comment + + use Inline Java => <<'END_OF_JAVA_CODE' ; + class Pod_alu { + public Pod_alu(){ + } + + public int add(int i, int j){ + return i + j ; + } + + public int subtract(int i, int j){ + return i - j ; + } + } + END_OF_JAVA_CODE + + my $alu = new Pod_alu() ; + print($alu->add(9, 16) . "\n") ; # prints 25 + print($alu->subtract(9, 16) . "\n") ; # prints -7 + +=for comment + + +=head1 DESCRIPTION + +The C<Inline::Java> module allows you to put Java source code +directly "inline" in a Perl script or module. A Java compiler +is launched and the Java code is compiled. Then Perl asks the +Java classes what public methods have been defined. These classes +and methods are available to the Perl program as if they had been +written in Perl. + +The process of interrogating the Java classes for public methods +occurs the first time you run your Java code. The namespace is +cached, and subsequent calls use the cached version. + Z<> + + +=head1 USING THE Inline::Java MODULE + +C<Inline::Java> is driven by fundamentally the same idea as other +C<Inline> language modules, like C<Inline::C> or C<Inline::CPP>. +Because Java is both compiled and interpreted, the method of getting +your code is different, but overall, using C<Inline::Java> is very similar +to any other C<Inline> language module. + +This section will explain the different ways to C<use> Inline::Java. +For more details on C<Inline>, see 'perldoc Inline'. + +B<Basic Usage> + +The most basic form for using C<Inline::Java> is: + + use Inline Java => 'Java source code' ; + +Of course, you can use Perl's "here document" style of quoting to make +the code slightly easier to read: + + use Inline Java => <<'END'; + + Java source code goes here. + + END + +The source code can also be specified as a filename, a subroutine +reference (sub routine should return source code), or an array +reference (array contains lines of source code). This information +is detailed in 'perldoc Inline'. + +In order for C<Inline::Java> to function properly, it needs to know +where to find a Java 2 SDK on your machine. This is done using one +of the following techniques: + +=over 4 + +=item 1 + +Set the J2SDK configuration option to the correct directory + +=item 2 + +Set the PERL_INLINE_JAVA_J2SDK environment variable to the +correct directory + +=back + + +If none of these are specified, C<Inline::Java> will use the Java +2 SDK that was specified a install time (see below). + + +=head1 DEFAULT JAVA 2 SDK + +When C<Inline::Java> was installed, the path to the Java 2 SDK that was +used was stored in a file called default_j2sdk.pl that resides with +the C<Inline::Java> module. You can find this file by using the following +command: + + % perl -MInline::Java=j2sdk + +If you wish to permanently change the default Java 2 SDK that is used +by C<Inline::Java>, edit this file and change the value found there. +If you wish use a different Java 2 SDK temporarily, see the J2SDK +configuration option described below. + +Additionally, you can use the following command to get the list of directories +that you should put in you shared library path when using the JNI extension: + + % perl -MInline::Java=so_dirs + + +=head1 CONFIGURATION OPTIONS + +There are a number of configuration options that dictate the +behavior of C<Inline::Java>: + +=over 4 + +=item J2SDK + +Specifies the path to your Java 2 SDK. + + Ex: J2SDK => '/my/java/2/sdk/path' + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item PORT + +Specifies the port number for the server. Default is -1 (next +available port number), default for SHARED_JVM mode is 7891. + + Ex: PORT => 4567 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item HOST + +Specifies the host on which the JVM server is running. This option +really only makes sense in SHARED_JVM mode when START_JVM is disabled. + + Ex: HOST => 'jvm.server.com' + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item BIND + +Specifies the IP address on which the JVM server will be listening. By +default the JVM server listens for connections on 'localhost' only. + + Ex: BIND => '192.168.1.1' + Ex: BIND => '0.0.0.0' + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item STARTUP_DELAY + +Specifies the maximum number of seconds that the Perl script +will try to connect to the Java server. In other this is the +delay that Perl gives to the Java server to start. Default +is 15 seconds. + + Ex: STARTUP_DELAY => 20 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item CLASSPATH + +Adds the specified CLASSPATH. This CLASSPATH will only be available +through the user classloader. To set the CLASSPATH globally (which is +most probably what you want to do anyways), use the CLASSPATH +environment variable. + + Ex: CLASSPATH => '/my/other/java/classses' + +=item JNI + +Toggles the execution mode. The default is to use the client/server +mode. To use the JNI extension (you must have built it at install +time though. See README and README.JNI for more information), set +JNI to 1. + + Ex: JNI => 1 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item EXTRA_JAVA_ARGS, EXTRA_JAVAC_ARGS + +Specify extra command line parameters to be passed to, respectively, +the JVM and the Java compiler. Use with caution as some options may +alter normal C<Inline::Java> behavior. + + Ex: EXTRA_JAVA_ARGS => '-Xmx96m' + +Note: EXTRA_JAVA_ARGS only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item EMBEDDED_JNI + +Same as JNI, except C<Inline::Java> expects the JVM to already be +loaded and to have loaded the Perl interpreter that is running the +script. This is an advanced feature that should only be need in +very specific circumstances. + + Ex: EMBEDDED_JNI => 1 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. Also, the EMBEDDED_JNI option automatically +sets the JNI option. + +=item SHARED_JVM + +This mode enables mutiple processes to share the same JVM. It was +created mainly in order to be able to use C<Inline::Java> under +mod_perl. + + Ex: SHARED_JVM => 1 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item START_JVM + +When used with SHARED_JVM, tells C<Inline::Java> that the JVM should +already be running and that it should not attempt to start a new +one. This option is useful in combination with command line interface +described in the BUGS AND DEFICIENCIES section. Default is 1. + + Ex: START_JVM => 0 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item PRIVATE + +In SHARED_JVM mode, makes every connection to the JVM use a different +classloader so that each connection is isolated from the others. + + Ex: PRIVATE => 1 + +Note: This configuration option only has an effect on the first +'use Inline Java' call inside a Perl script, since all other calls +make use of the same JVM. + +=item DEBUG + +Enables debugging info. Debugging now uses levels (1 through 5) +that (loosely) follow these definitions: + + 1 = Major program steps + 2 = Object creation/destruction + 3 = Method/member accesses + packet dumps + 4 = Everything else + 5 = Data structure dumps + + Ex: DEBUG => 2 + +=item DEBUGGER + +Starts jdb, (the Java debugger) instead of the regular Java JVM. +This option will also cause the Java code to be compiled using the +'-g' switch for extra debugging information. EXTRA_JAVA_ARGS can +be used use to pass extra options to the debugger. + + Ex: DEBUGGER => 1 + +=item WARN_METHOD_SELECT + +Throws a warning when C<Inline::Java> has to 'choose' between +different method signatures. The warning states the possible +choices and the signature chosen. + + Ex: WARN_METHOD_SELECT => 1 + +=item STUDY + +Takes an array of Java classes that you wish to have +C<Inline::Java> learn about so that you can use them inside Perl. + + Ex: STUDY => ['java.lang.HashMap', 'my.class'] + +=item AUTOSTUDY + +Makes C<Inline::Java> automatically study unknown classes it +encounters them. + + Ex: AUTOSTUDY => 1 + +=item PACKAGE + +Forces C<Inline::Java> to bind the Java code under the specified +package instead of under the current (caller) package. + + Ex: PACKAGE => 'main' + +=item NATIVE_DOUBLES + +Normally, C<Inline::Java> stringifies floating point numbers when passing +them between Perl and Java. In certain cases, this can lead to loss of +precision. When NATIVE_DOUBLES is set, C<Inline::Java> will send the actual +double bytes in order to preserve precision. +Note: This applies only to doubles, not floats. +Note: This option may not be portable and may not work properly on some +platforms. + + Ex: NATIVE_DOUBLES => 1 + +=back + + +=head1 ENVIRONMENT VARIABLES + +Every configuration option listed above, with the exception of STUDY, +can be specified using an environment variable named using the +following convention: + + PERL_INLINE_JAVA_<option name> + +For example, your can specified the JNI option usng the +PERL_INLINE_JAVA_JNI environment variable. + +Note that environment variables take precedence over options specified +in the script itself. + +Under Win32, you can also use set the PERL_INLINE_JAVA_COMMAND_COM +environment variable to a true value to indicate that you are using +the command.com shell. However, C<Inline::Java> should normally be +able to determine this on its own. + + +=head1 CLASSES AND OBJECTS + +Because Java is object oriented, any interface between Perl and Java +needs to support Java classes adequately. + +Example: + +=for comment + + use Inline Java => <<'END' ; + class Pod_1 { + String data = "data" ; + static String sdata = "static data" ; + + public Pod_1(){ + } + + public String get_data(){ + return data ; + } + + public static String get_static_data(){ + return sdata ; + } + + public void set_data(String d){ + data = d ; + } + + private void priv(){ + } + } + END + + my $obj = new Pod_1 ; + print($obj->get_data() . "\n") ; # prints data + $obj->set_data("new data") ; + print($obj->get_data() . "\n") ; # prints new data + +=for comment + +C<Inline::Java> created a new namespace called C<main::Pod_1> and +created the following functions: + + sub main::Pod_::new { ... } + sub main::Pod_::Pod_1 { ... } + sub main::Pod_::get_data { ... } + sub main::Pod_::get_sdata { ... } + sub main::Pod_::set_data { ... } + sub main::Pod_::DESTROY { ... } + +Note that only the public methods are exported to Perl. + +Inner classes are also supported, you simply need to supply a reference +to an outer class object as the first parameter of the constructor: + +=for comment + + use Inline Java => <<'END' ; + class Pod_2 { + public Pod_2(){ + } + + public class Pod_2_Inner { + public String name = "Pod_2_Inner" ; + + public Pod_2_Inner(){ + } + } + } + END + + my $obj = new Pod_2() ; + my $obj2 = new Pod_2::Pod_2_Inner($obj) ; + print($obj2->{name} . "\n") ; # prints Pod_2_Inner + +=for comment + +=head1 METHODS + +In the previous example we have seen how to call a method. You can also +call static methods in the following manner: + + print Pod_1->get_sdata() . "\n" ; # prints static data + # or + my $obj = new Pod_1() ; + print $obj->get_sdata() . "\n" ; # prints static data + +You can pass any kind of Perl scalar or any Java object to a method. It +will be automatically converted to the correct type: + +=for comment + + use Inline Java => <<'END' ; + class Pod_3_arg { + public Pod_3_arg(){ + } + } + class Pod_3 { + public int n ; + + public Pod_3(int i, String j, Pod_3_arg k) { + n = i ; + } + } + END + + my $obj = new Pod_3_arg() ; + my $obj2 = new Pod_3(5, "toto", $obj) ; + print($obj2->{n} . "\n") ; # prints 5 + +=for comment + +will work fine. These objects can be of any type, even if these types +are not known to C<Inline::Java>. This is also true for return types: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + + class Pod_4 { + public Pod_4(){ + } + + public HashMap get_hash(){ + HashMap h = new HashMap() ; + h.put("key", "value") ; + + return h ; + } + + public String do_stuff_to_hash(HashMap h){ + return (String)h.get("key") ; + } + } + END + + my $obj = new Pod_4() ; + my $h = $obj->get_hash() ; + print($obj->do_stuff_to_hash($h) . "\n") ; # prints value + +=for comment + +Objects of types unknown to Perl can exist in the Perl space, you just +can't call any of their methods. See the STUDYING section for more +information on how to tell C<Inline::Java> to learn about these classes. + Z<> + + +=head1 MEMBER VARIABLES + +You can also access all public member variables (static or not) from Perl. +As with method arguments, the types of these variables does not need to +be known to Perl: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + + class Pod_5 { + public int i ; + public static HashMap hm ; + + public Pod_5(){ + } + } + END + + my $obj = new Pod_5() ; + $obj->{i} = 2 ; + print($obj->{i} . "\n") ; # prints 2 + my $hm1 = $obj->{hm} ; # instance way + my $hm2 = $Pod_4::hm ; # static way + +=for comment + +Note: Watch out for typos when accessing members in the static fashion, +'use strict' will not catch them since they have a package name... + Z<> + + +=head1 ARRAYS + +You can also send, receive and modify arrays. This is done simply by +using Perl lists: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + + class Pod_6 { + public int i[] = {5, 6, 7} ; + + public Pod_6(){ + } + + public String [] f(String a[]){ + return a ; + } + + public String [][] f(String a[][]){ + return a ; + } + } + END + + my $obj = new Pod_6() ; + my $i_2 = $obj->{i}->[2] ; # 7 + print($i_2 . "\n") ; # prints 7 + + my $a1 = $obj->f(["a", "b", "c"]) ; # String [] + my $a2 = $obj->f([ + ["00", "01"], + ["10", "11"], + ]) ; # String [][] + print($a2->[1]->[0] . "\n") ; # prints 10 + +=for comment + + +=head1 EXCEPTIONS + +You can now (as of 0.31) catch exceptions as objects when they are thrown +from Java. To do this you use the regular Perl exception tools: eval and +$@. A helper function named 'caught' is provided to help determine the +type of the exception. Here is a example of a typical use: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + + class Pod_9 { + public Pod_9(boolean t) throws Exception { + if (t){ + throw new Exception("ouch!") ; + } + } + } + END + + use Inline::Java qw(caught) ; + + eval { + my $obj = new Pod_9(1) ; + } ; + if ($@){ + if (caught("java.lang.Exception")){ + my $msg = $@->getMessage() ; + print($msg . "\n") ; # prints ouch! + } + else{ + # It wasn't a Java exception after all... + die $@ ; + } + } + +=for comment + +What's important to understand is that $@ actually contains a reference +to the Throwable object that was thrown by Java. The getMessage() function +is really a method of the java.lang.Exception class. So if Java is throwing +a custom exception you have in your code, you will have access to that +exception object's public methods just like any other Java object in +C<Inline::Java>. +Note: C<Inline::Java> uses eval under the hood, so it recommended that you +store any exception in a temporary variable before processing it, especially +f you will be calling other C<Inline::Java> functions. It is also probably +a good idea to undef $@ once you have treated a Java exception, or else +the object still has a reference until $@ is reset by the next eval. + Z<> + + +=head1 FILEHANDLES + +Java filehandles (java.io.Reader, java.io.Writer, java.io.InputStream or +java.io.OutputStream objects) can be wrapped the C<Inline::Java::Handle> +class to allow reading or writing from Perl. Here's an example: + +=for comment + + use Inline Java => <<'END' ; + import java.io.* ; + + class Pod_91 { + public static Reader getReader(String file) throws FileNotFoundException { + return new FileReader(file) ; + } + } + END + + my $o = Pod_91->getReader('data.txt') ; + my $h = new Inline::Java::Handle($o) ; + while (<$h>){ + chomp($_) ; + print($_ . "\n") ; # prints data + } + + +=for comment + +What's important to understand is that $@ actually contains a reference +to the Throwable object that was thrown by Java. The getMessage() function +is really a method of the java.lang.Exception class. So if Java is throwing +a custom exception you have in your code, you will have access to that +exception object's public methods just like any other Java object in +C<Inline::Java>. It is also probably a good idea to undef $@ once you have +treated a Java exception, or else the object still has a reference until +$@ is reset by the next eval. + Z<> + + +=head1 CALLBACKS + +See L<Inline::Java::Callbacks> for more information on making callbacks. + + +=head1 STUDYING + +As of version 0.21, C<Inline::Java> can learn about other Java classes +and use them just like the Java code you write inside your Perl script. +In fact you are not even required to write Java code inside your Perl +script anymore. Here's how to use the 'studying' function: + +=for comment + + use Inline ( + Java => 'STUDY', + STUDY => ['java.util.HashMap'], + ) ; + + my $hm = new java::util::HashMap() ; + $hm->put("key", "value") ; + my $val = $hm->get("key") ; + print($val . "\n") ; # prints value + +=for comment + +If you do not wish to put any Java code inside you Perl script, you must +use the string 'STUDY' as your code. This will skip the build section. + +You can also use the AUTOSTUDY option to tell C<Inline::Java> that you wish +to study all classes that it comes across: + +=for comment + + use Inline Java => <<'END', AUTOSTUDY => 1 ; + import java.util.* ; + + class Pod_10 { + public Pod_10(){ + } + + public HashMap get_hm(){ + HashMap hm = new HashMap() ; + return hm ; + } + } + END + + my $obj = new Pod_10() ; + my $hm = $obj->get_hm() ; + $hm->put("key", "value") ; + my $val = $hm->get("key") ; + print($val . "\n") ; # prints value + +=for comment + +In this case C<Inline::Java> intercepts the return value of the get_hm() +method, sees that it's of a type that it doesn't know about +(java.lang.HashMap), and immediately studies the class. After that call +the java::lang::HashMap class is available to use through Perl. + +In some cases you may not know which classes to study until runtime. In +these cases you can use the study_classes() function: + +=for comment + + use Inline ( + Java => 'STUDY', + STUDY => [], + ) ; + use Inline::Java qw(study_classes) ; + + study_classes(['java.util.HashMap'], undef) ; + my $hm = new java::util::HashMap() ; + $hm->put("key", "value") ; + my $val = $hm->get("key") ; + print($val . "\n") ; # prints value + +=for comment + +The study_classes() function takes 2 arguments, a reference to an array of +class names (like the STUDY configuration option) and the name of the +package in which to bind those classes. If the name of the package is +undefined, the classes will be bound to the current (caller) package. + +Note: You can only specify the names of packages in which you have +previously "used" C<Inline::Java>. + Z<> + + +=head1 TYPE CASTING + +Sometimes you need to manipulate a Java object using a specific +subtype. That's when type casting is necessary. Here's an +example of this: + +=for comment + + use Inline ( + Java => 'STUDY', + STUDY => ['java.util.HashMap'], + AUTOSTUDY => 1, + ) ; + use Inline::Java qw(cast) ; + + my $hm = new java::util::HashMap() ; + $hm->put('key', 'value') ; + + my $entries = $hm->entrySet()->toArray() ; + foreach my $e (@{$entries}){ + # print($e->getKey() . "\n") ; # No! + print(cast('java.util.Map$Entry', $e)->getKey() . "\n") ; # prints key + } + +=for comment + +In this case, C<Inline::Java> knows that $e is of type java.util.HashMap$Entry. +The problem is that this type is not public, and therefore we can't access +the object through that type. We must cast it to a java.util.Map$Entry, which +is a public interface and will allow us to access the getKey() method. + +You can also use type casting to force the selection of a specific method +signature for methods that have multiple signatures. See examples similar +to this in the "TYPE COERCING" section below. + + +=head1 TYPE COERCING + +Type coercing is the equivalent of casting for primitives types +and arrays. It is used to force the selection if a specific method +signature when C<Inline::Java> has multiple choices. The coerce +function returns a special object that can only be used when calling +Java methods or assigning Java members. Here is an example: + +=for comment + + use Inline Java => <<'END' ; + class Pod_101 { + public Pod_101(){ + } + + public String f(int i){ + return "int" ; + } + + public String f(char c){ + return "char" ; + } + } + END + + my $obj = new Pod_101() ; + print($obj->f('5') . "\n") ; # prints int + +=for comment + +In this case, C<Inline::Java> will call f(int i), because '5' is an integer. +But '5' is a valid char as well. So to force the call of f(char c), do the +following: + + use Inline::Java qw(coerce) ; + $obj->f(coerce('char', '5')) ; + # or + $obj->f(Inline::Java::coerce('char', '5')) ; + +The coerce function forces the selection of the matching signature. Note that +the coerce must match the argument type exactly. Coercing to a class that +extends the argument type will not work. + +Another case where type coercing is needed is when one wants to pass an array +as a java.lang.Object: + + use Inline Java => <<'END'; + class Pod_8 { + public Object o ; + int a[] = {1, 2, 3} ; + + public Pod_8() { + } + } + END + + my $obj = new Pod_8() ; + $obj->{o} = [1, 2, 3] ; # No! + +The reason why this will not work is simple. When C<Inline::Java> sees an +array, it checks the Java type you are trying to match it against to validate +the construction of your Perl list. But in this case, it can't validate +the array because you're assigning it to an Object. You must use the 3 +parameter version of the coerce function to do this: + + $obj->{o} = Inline::Java::coerce( + "java.lang.Object", + [1, 2, 3], + "[Ljava.lang.String;") ; + +This tells C<Inline::Java> to validate your Perl list as a String [], and +then coerce it as an Object. + +Here is how to construct the array type representations: + + [<type> -> 1 dimensional <type> array + [[<type> -> 2 dimensional <type> array + ... + + where <type> is one of: + B byte S short I int J long + F float D double C char Z boolean + + L<class>; array of <class> objects + +This is described in more detail in most Java books that talk about +reflection. + +But you only need to do this if you have a Perl list. If you already have a +Java array reference obtained from elsewhere, you don't even need to coerce: + + $obj->{o} = $obj->{a} ; + + +=head1 JNI vs CLIENT/SERVER MODES + +Starting in version 0.20, it is possible to use the JNI (Java Native +Interface) extension. This enables C<Inline::Java> to load the Java virtual +machine as a shared object instead of running it as a stand-alone server. +This brings an improvement in performance. + +If you have built the JNI extension, you must enable it explicitely by doing +one of the following: + +=over 4 + +=item 1 + +Set the JNI configuration option to 1 + +=item 2 + +Set the PERL_INLINE_JAVA_JNI environment variable to 1 + +=back + + +Note: C<Inline::Java> only creates one virtual machine instance. Therefore +you can't use JNI for some sections and client/server for others. The first +section determines the execution mode. + +See README.JNI for more information about the JNI extension. + Z<> + + +=head1 SHARED_JVM + +Starting with version 0.30, the C<Inline::Java> JVM can now be shared between +multiple processes. The first process to start creates the JVM but does not +shut it down on exit. All other processes can then connect as needed to the JVM. +If any of these other processes where created by forking the parent process, +the Inline::Java->reconnect_JVM() function must be called in the child to get +a fresh connection to the JVM. Ex: + +=for comment + + use Inline ( + Java => <<'END', + class Pod_11 { + public static int i = 0 ; + public Pod_11(){ + i++ ; + } + } + END + SHARED_JVM => 1, + ) ; + + my $nb = 5 ; + for (my $i = 0 ; $i < $nb ; $i++){ + if (! fork()){ + Inline::Java::reconnect_JVM() ; + my $f = new Pod_11() ; + exit ; + } + } + sleep(5) ; + + my $f = new Pod_11() ; + print($f->{i} . "\n") ; # prints 6 + +=for comment + +Once this code was run, each of the 6 processes will have created a different +instance of the 't' class. Data can be shared between the processes by using +static members in the Java code. + +Note: The Java System.out stream is closed in SHARED_JVM mode. + Z<> + + +=head1 USING Inline::Java IN A CGI + +If you want to use C<Inline::Java> in a CGI script, do the following: + +=for comment + + use CGI ; + use Inline ( + Java => <<'END', + class Pod_counter { + public static int cnt = 0 ; + public Pod_counter(){ + cnt++ ; + } + } + END + SHARED_JVM => 1, + DIRECTORY => '/somewhere/your/web/server/can/write', + ) ; + + my $c = new Pod_counter() ; + my $q = new CGI() ; + print + $q->start_html() . + "This page has been accessed " . $c->{cnt} . " times." . + $q->end_html() ; + +=for comment + +In this scenario, the first CGI to execute will start the JVM, but does +not shut it down on exit. Subsequent CGI, since they have the SHARED_JVM +option enabled, will try to connect to the already existing JVM before +trying to start a new one. Therefore if the JVM happens to crash or is +killed, the next CGI that runs will start a new one. The JVM will be +killed when Apache is shut down. + +See the BUGS AND DEFICIENCIES section if you have problems starting +the SHARED_JVM server in a CGI. + Z<> + + +=head1 USING Inline::Java UNDER MOD_PERL + +Here is an example of how to use C<Inline::Java> under mod_perl: + + use Apache2::Const qw(:common) ; + use Inline ( + Java => <<'END', + class Pod_counter { + public static int cnt = 0 ; + public Pod_counter(){ + cnt++ ; + } + } + END + SHARED_JVM => 1, + DIRECTORY => '/somewhere/your/web/server/can/write', + ) ; + + my $c = new Pod_counter() ; + + sub handler { + my $r = shift ; + + my $q = new CGI ; + print + $q->start_html() . + "This page has been accessed " . $c->{cnt} . " times." . + $q->end_html() ; + + return OK ; + } + +See USING Inline::Java IN A CGI for more details. + +If you are using L<ModPerl::Registry>, make sure to use the C<PACKAGE> +configuration option to specifiy the package in which C<Inline::Java> +should bind the Java code, since L<ModPerl::Registry> will place your +code in a package with a unpredictable name. + +See the BUGS AND DEFICIENCIES section if you have problems starting +the SHARED_JVM server under MOD_PERL. + +=head2 Preloading and PerlChildInitHandler + +If you are loading C<Inline::Java> during your server startup (common practice to +increase shared memory and reduce run time) and you are using C<SHARED_JVM>, then +your Apache processes will all share the same socktd connection to that JVM. +This will result in garbled communication and strange errors (like "Can't receive packet from JVM", "Broken pipe", etc). + +To fix this you need to tell Apache that after each child process has forked they +each need to create their own connections to the JVM. This is done during the +C<ChildInit> stage. + +For Apache 1.3.x this could look like: + + # in httpd.conf + PerlChildInitHandler MyProject::JavaReconnect + +And C<MyProject::JavaReconnect> could be as simple as this: + + package MyProject::JavaReconnect; + sub handler($$) { Inline::Java::reconnect_JVM() } + 1; + + +=head1 BUGS AND DEFICIENCIES + +When reporting a bug, please do the following: + + - Put "use Inline REPORTBUG;" at the top of your code, or + use the command line option "perl -MInline=REPORTBUG ...". + - Run your code. + - Follow the printed instructions. + +Here are some things to watch out for: + +=over 4 + +=item 1 + +You shouldn't name any of your classes 'B', 'S', 'I', 'J', 'F', 'D', +'C', 'Z' or 'L'. These classes seem to be used internally by Java to +represent the primitive types. + +=item 2 + +If you upgrade C<Inline::Java> from a previous version, be sure to delete +your _Inline directory so that C<Inline::Java>'s own Java classes get +rebuilt to match the Perl code. + +=item 3 + +Under certain environments, i.e. CGI or mod_perl, the JVM cannot start +properly because of the way these environments set up STDIN and STDOUT. +In these cases, you may wish to control the JVM (in shared mode) manually +using the following commands: + + % perl -MInline::Java::Server=status + % perl -MInline::Java::Server=start + % perl -MInline::Java::Server=stop + % perl -MInline::Java::Server=restart + +You can specify C<Inline::Java> options by setting the proper +environment variables, and you can also set the _Inline directory by using +the PERL_INLINE_JAVA_DIRECTORY environment variable. + +In addition, you may also wish to set the START_JVM option to 0 in your scripts +to prevent them from trying to start their own JVM if they can't find one, +thereby causing problems. + +=item 4 + +Because of problems with modules C<Inline::Java> depends on, the usage of paths +containing spaces is not fully supported on all platforms. This applies to the +installation directory as well as the path for J2SDK and CLASSPATH elements. + +=item 5 + +Even though it in run through a profiler regularly, C<Inline::Java> is relatively +slow compared to native Perl or Java. + + +=back + + +=head1 SEE ALSO + +L<Inline::Java::Callback>, L<Inline::Java::PerlNatives>, +L<Inline::Java::PerlInterpreter>. + +For information about using C<Inline>, see L<Inline>. + +For information about other Inline languages, see L<Inline-Support>. + +C<Inline::Java>'s mailing list is <inline@perl.org>. +To subscribe, send an email to <inline-subscribe@perl.org> + Z<> + + +=head1 AUTHOR + +Patrick LeBoutillier <patl@cpan.org> is the author of Inline::Java. + +Brian Ingerson <ingy@cpan.org> is the author of Inline. + Z<> + + +=head1 COPYRIGHT + +Copyright (c) 2001-2005, Patrick LeBoutillier. + +All Rights Reserved. This module is free software. It may be used, +redistributed and/or modified under the terms of the Perl Artistic +License. See http://www.perl.com/perl/misc/Artistic.html for more +details. + +=cut diff --git a/Java/Array.pm b/Java/Array.pm new file mode 100644 index 0000000..b98a7d6 --- /dev/null +++ b/Java/Array.pm @@ -0,0 +1,634 @@ +package Inline::Java::Array ; +@Inline::Java::Array::ISA = qw(Inline::Java::Array::Tie) ; + +use strict ; +use Carp ; + +$Inline::Java::Array::VERSION = '0.53' ; + +# Here we store as keys the knots and as values our blessed objects +my $OBJECTS = {} ; + + +sub new { + my $class = shift ; + my $object = shift ; + + my @this = () ; + my $knot = tie @this, $class ; + my $this = bless (\@this, $class) ; + + $OBJECTS->{$knot} = $object ; + + Inline::Java::debug(5, "this = '$this'") ; + Inline::Java::debug(5, "knot = '$knot'") ; + + return $this ; +} + + +sub __get_object { + my $this = shift ; + + my $knot = tied @{$this} || $this ; + + my $ref = $OBJECTS->{$knot} ; + if (! defined($ref)){ + croak "Unknown Java array reference '$knot'" ; + } + + return $ref ; +} + + +sub __isa { + my $this = shift ; + my $proto = shift ; + + return $this->__get_object()->__isa($proto) ; +} + + +sub length { + my $this = shift ; + + my $obj = $this->__get_object() ; + + my $ret = undef ; + eval { + # Check the cached value + $ret = $obj->__get_private()->{array_length} ; + if (! defined($ret)){ + $ret = $obj->__get_private()->{proto}->CallJavaMethod('getLength', [], []) ; + $obj->__get_private()->{array_length} = $ret ; + } + else{ + Inline::Java::debug(4, "using cached array length $ret") ; + } + } ; + croak $@ if $@ ; + + return $ret ; +} + + +sub __get_element { + my $this = shift ; + my $idx = shift ; + + my $max = $this->length() - 1 ; + if ($idx > $max){ + croak("Java array index out of bounds ($idx > $max)") + } + + my $obj = $this->__get_object() ; + + my $ret = undef ; + eval { + $ret = $obj->__get_private()->{proto}->GetJavaMember($idx, ['java.lang.Object'], [undef]) ; + } ; + croak $@ if $@ ; + + return $ret ; +} + + +sub __set_element { + my $this = shift ; + my $idx = shift ; + my $s = shift ; + + my $max = $this->length() - 1 ; + if ($idx > $max){ + croak("Java array index out of bounds ($idx > $max)") + } + + my $obj = $this->__get_object() ; + + # Now we need to find out if what we are trying to set matches + # the array. + my $java_class = $obj->__get_private()->{java_class} ; + my $elem_class = $java_class ; + my $an = Inline::Java::Array::Normalizer->new($obj->__get_private()->{inline}, $java_class) ; + if ($an->{req_nb_dim} > 1){ + $elem_class =~ s/^\[// ; + } + else{ + $elem_class = $an->{req_element_class} ; + } + + my $ret = undef ; + eval { + my ($new_args, $score) = Inline::Java::Class::CastArguments([$s], [$elem_class], $obj->__get_private()->{inline}) ; + $ret = $obj->__get_private()->{proto}->SetJavaMember($idx, [$elem_class], $new_args) ; + } ; + croak $@ if $@ ; + + return $ret ; +} + + +sub AUTOLOAD { + my $this = shift ; + my @args = @_ ; + + use vars qw($AUTOLOAD) ; + my $func_name = $AUTOLOAD ; + # Strip package from $func_name, Java will take of finding the correct + # method. + $func_name =~ s/^(.*)::// ; + + croak "Can't call method '$func_name' on Java arrays" ; +} + + +sub DESTROY { + my $this = shift ; + + my $knot = tied @{$this} ; + if (! $knot){ + Inline::Java::debug(4, "destroying Inline::Java::Array::Tie") ; + + $OBJECTS->{$this} = undef ; + } + else{ + Inline::Java::debug(4, "destroying Inline::Java::Array") ; + } +} + + + + +######################## Array methods ######################## +package Inline::Java::Array::Tie ; +@Inline::Java::Array::Tie::ISA = qw(Tie::StdArray) ; + + +use Tie::Array ; +use Carp ; + + +sub TIEARRAY { + my $class = shift ; + + return $class->SUPER::TIEARRAY(@_) ; +} + + +sub FETCHSIZE { + my $this = shift ; + + my $array = $this->length() ; +} + + +sub STORE { + my $this = shift ; + my $idx = shift ; + my $s = shift ; + + return $this->__set_element($idx, $s) ; +} + + +sub FETCH { + my $this = shift ; + my $idx = shift ; + + return $this->__get_element($idx) ; +} + + +sub EXISTS { + my $this = shift ; + my $idx = shift ; + + return $this->SUPER::EXISTS($idx) ; +} + + +sub STORESIZE { + my $this = shift ; + my $size = shift ; + + croak "Operation STORESIZE not supported on Java array" ; +} + + +sub CLEAR { + my $this = shift ; + + croak "Operation CLEAR not supported on Java array" ; +} + + +sub POP { + my $this = shift ; + + croak "Operation POP not supported on Java array" ; +} + + +sub PUSH { + my $this = shift ; + my @list = @_ ; + + croak "Operation PUSH not supported on Java array" ; +} + + +sub SHIFT { + my $this = shift ; + + croak "Operation SHIFT not supported on Java array" ; +} + + +sub UNSHIFT { + my $this = shift ; + my @list = @_ ; + + croak "Operation UNSHIFT not supported on Java array" ; +} + + +sub DELETE { + my $this = shift ; + my $idx = shift ; + + croak "Operation DELETE not supported on Java array" ; +} + + +sub EXTEND { + my $this = shift ; + my $count = shift ; + + croak "Operation EXTEND not supported on Java array" ; +} + + +sub SPLICE { + my $this = shift ; + my $offset = shift ; + my $length = shift ; + my @LIST = @_ ; + + croak "Operation SPLICE not supported on Java array" ; +} + + +sub DESTROY { + my $this = shift ; +} + + + +######################## Inline::Java::Array::Normalizer ######################## +package Inline::Java::Array::Normalizer ; + + +use Carp ; + + +sub new { + my $class = shift ; + my $inline = shift ; + my $java_class = shift ; + my $ref = shift ; + + if (! Inline::Java::Class::ClassIsArray($java_class)){ + croak "Can't create Java array of non-array class '$java_class'" ; + } + + my $this = {} ; + $this->{class} = $class ; + $this->{java_class} = $java_class ; + $this->{map} = {} ; + $this->{ref} = $ref ; + $this->{array} = [] ; + $this->{score} = 0 ; + $this->{inline} = $inline ; + + bless ($this, $class) ; + + # The first thing we want to do is figure out what kind of array we want, + # and how many dimensions it should have. + $this->AnalyzeArrayClass() ; + + if ($ref){ + $this->InitFromArray($this->{array}) ; + } + + return $this ; +} + + +sub InitFromArray { + my $this = shift ; + my $array = shift ; + my $level = shift ; + + my $ref = $this->{ref} ; + + $this->ValidateArray($ref, $array) ; + + if (! $level){ + Inline::Java::debug_obj($this) ; + } +} + + +sub InitFromFlat { + my $this = shift ; + my $inline = shift ; + my $dims = shift ; + my $list = shift ; + my $level = shift ; + + my $nb_list = scalar(@{$list}) ; + my $parts = $dims->[0] ; + + my $req_nb_elem = 1 ; + foreach my $d (@{$dims}){ + $req_nb_elem *= $d ; + } + if ($req_nb_elem != $nb_list){ + my $ds = "[" . join("][", @{$dims}) . "]" ; + croak "Corrupted array: $ds should contain $req_nb_elem elements, has $nb_list" ; + } + + for (my $i = 0 ; $i < $parts ; $i++){ + my $elem = undef ; + if (scalar(@{$dims}) == 1){ + # We are at the bottom of the list. + $elem = $list->[$i] ; + } + else{ + my $nb_elems = $nb_list / $parts ; + my @sub = splice(@{$list}, 0, $nb_elems) ; + + my $java_class = $this->{java_class} ; + $java_class =~ s/^\[// ; + + my @dims = @{$dims} ; + shift @dims ; + my $obj = Inline::Java::Array::Normalizer->new($inline, $java_class) ; + $obj->InitFromFlat($inline, \@dims, \@sub, $level + 1) ; + $elem = $obj->{array} ; + } + my $nb = scalar(@{$this->{array}}) ; + $this->{array}->[$nb] = $elem ; + } + + if (! $level){ + # Inline::Java::debug_obj($this) ; + } +} + + +# Checks if the contents of the Array match the ones prescribed +# by the Java prototype. +sub AnalyzeArrayClass { + my $this = shift ; + + my $java_class = $this->{java_class} ; + + my ($depth_str, $type, $class) = Inline::Java::Class::ValidateClassSplit($java_class) ; + $depth_str =~ /^(\[+)/ ; + my $depth = length($depth_str) ; + + my %map = ( + B => 'byte', + S => 'short', + I => 'int', + J => 'long', + F => 'float', + D => 'double', + C => 'char', + Z => 'boolean', + L => $class, + ) ; + + my $pclass = $map{$type} ; + if (! $pclass){ + croak "Can't determine array type for '$java_class'" ; + } + + $this->{req_element_class} = $pclass ; + $this->{req_nb_dim} = $depth ; + + return ; +} + + +# This method makes sure that we have a valid array that +# can be used in a Java function. It will return an array +# That contains either all scalars or all object references +# at the lowest level. +sub ValidateArray { + my $this = shift ; + my $ref = shift ; + my $array = shift ; + my $level = shift || 0 ; + + if (! ((defined($ref))&&(UNIVERSAL::isa($ref, "ARRAY")))){ + # We must start with an array of some kind... + croak "'$ref' is not an array reference" ; + } + + my $map = $this->{map} ; + if (! exists($map->{$level}->{max})){ + $map->{$level}->{max} = 0 ; + } + $this->ValidateElements($ref, $array, $level) ; + + foreach my $elem (@{$ref}){ + if ((defined($elem))&&(UNIVERSAL::isa($elem, "ARRAY"))){ + # All the elements at this level are sub-arrays. + my $sarray = [] ; + $this->ValidateArray($elem, $sarray, $level + 1) ; + push @{$array}, $sarray ; + } + } + + $this->FillArray($array, $level) ; + + if (! $level){ + my @levels = (sort {$a <=> $b} keys %{$map}) ; + my $nbl = scalar(@levels) ; + + my $last = $levels[$nbl - 1] ; + my @dims = () ; + my $max_cells = 1 ; + foreach my $l (@levels){ + push @dims, $map->{$l}->{max} ; + $max_cells *= $map->{$l}->{max} ; + } + my $nb_cells = ($map->{$last}->{count} || 0) ; + Inline::Java::debug(4, "array is [" . join("][", @dims) . "]") ; + Inline::Java::debug(4, "array has $nb_cells declared cells") ; + Inline::Java::debug(4, "array should have $max_cells declared cells") ; + $this->{dim} = \@dims ; + $this->{nb_dim} = scalar(@dims) ; + + if ($this->{nb_dim} != $this->{req_nb_dim}){ + croak "Java array should have $this->{req_nb_dim} instead of " . + "$this->{nb_dim} dimensions" ; + } + + Inline::Java::debug_obj($this) ; + } +} + + +# Makes sure that all the elements are of the same type. +sub ValidateElements { + my $this = shift ; + my $ref = shift ; + my $array = shift ; + my $level = shift ; + + my $map = $this->{map} ; + my $cnt = scalar(@{$ref}) ; + my $max = $map->{$level}->{max} ; + if ($cnt > $max){ + $map->{$level}->{max} = $cnt ; + } + + for (my $i = 0 ; $i < $cnt ; $i++){ + my $elem = $ref->[$i] ; + if (defined($elem)){ + if (UNIVERSAL::isa($elem, "ARRAY")){ + $this->CheckMap("SUB_ARRAY", $level) ; + } + elsif ( + (UNIVERSAL::isa($elem, "Inline::Java::Object"))|| + (! ref($elem))){ + $this->CheckMap("BASE_ELEMENT", $level) ; + my @ret = $this->CastArrayArgument($elem) ; + $array->[$i] = $ret[0] ; + $this->{score} += $ret[1] ; + } + else{ + croak "A Java array can only contain scalars, Java objects or array references" ; + } + } + $map->{$level}->{count}++ ; + } +} + +sub CheckMap { + my $this = shift ; + my $type = shift ; + my $level = shift ; + + my $map = $this->{map} ; + + if (! exists($map->{$level}->{type})){ + $map->{$level}->{type} = $type ; + } + elsif ($map->{$level}->{type} ne $type){ + croak "Java array contains mixed types in dimension $level ($type != $map->{$level}->{type})" ; + } +} + + +sub CastArrayArgument { + my $this = shift ; + my $arg = shift ; + + my $element_class = $this->{req_element_class} ; + + my ($new_arg, $score) = Inline::Java::Class::CastArgument($arg, $element_class, $this->{inline}) ; + + return ($new_arg, $score) ; +} + + +# Makes sure that all the dimensions of the array have the same number of elements +sub FillArray { + my $this = shift ; + my $array = shift ; + my $level = shift ; + + my $map = $this->{map} ; + + my $max = $map->{$level}->{max} ; + my $nb = scalar(@{$array}) ; + + my $type = $map->{$level}->{type} ; + # Type can be undefined when array is zero length. + if ((defined($type))&&($type eq "SUB_ARRAY")){ + foreach my $elem (@{$array}){ + if (! defined($elem)){ + $elem = [] ; + $this->FillArray($elem, $level + 1) ; + } + } + } + + if ($nb < $max){ + # We must stuff... + for (my $i = $nb ; $i < $max ; $i++){ + if ((defined($type))&&($type eq "SUB_ARRAY")){ + my $elem = [] ; + $this->FillArray($elem, $level + 1) ; + push @{$array}, $elem ; + } + else{ + push @{$array}, undef ; + } + } + } +} + + +sub FlattenArray { + my $this = shift ; + my $level = shift ; + + my $list = $this->MakeElementList($this->{array}) ; + + my $dim = $this->{dim} ; + my $req_nb_elem = 1 ; + foreach my $d (@{$dim}){ + $req_nb_elem *= $d ; + } + + my $nb_elem = scalar(@{$list}) ; + if ($req_nb_elem != $nb_elem){ + my $ds = "[" . join("][", @{$dim}) . "]" ; + croak "Corrupted array: $ds should contain $req_nb_elem elements, has $nb_elem" ; + } + + my $ret = [$dim, $list] ; + + Inline::Java::debug_obj($ret) ; + + return $ret ; +} + + +sub MakeElementList { + my $this = shift ; + my $array = shift ; + + my @ret = () ; + foreach my $elem (@{$array}){ + if ((defined($elem))&&(UNIVERSAL::isa($elem, "ARRAY"))){ + my $sret = $this->MakeElementList($elem) ; + push @ret, @{$sret} ; + } + else{ + # All elements here are base level elements. + push @ret, @{$array} ; + last ; + } + } + + return \@ret ; +} + + +1 ; diff --git a/Java/Callback.pm b/Java/Callback.pm new file mode 100644 index 0000000..de3bd1d --- /dev/null +++ b/Java/Callback.pm @@ -0,0 +1,223 @@ +package Inline::Java::Callback ; + +use strict ; +use Carp ; + +$Inline::Java::Callback::VERSION = '0.53' ; + +$Inline::Java::Callback::OBJECT_HOOK = undef ; + + +my %OBJECTS = () ; +my $next_id = 1 ; + + +sub InterceptCallback { + my $inline = shift ; + my $resp = shift ; + + # With JNI we need to store the object somewhere since we + # can't drag it along all the way through Java land... + if (! defined($inline)){ + $inline = $Inline::Java::JNI::INLINE_HOOK ; + } + + if ($resp =~ s/^callback ([^ ]+) (\@?[\w:]+) ([^ ]+)//){ + my $via = $1 ; + my $function = $2 ; + my $cast_return = $3 ; + my @args = split(' ', $resp) ; + + # "Relative" namespace... + if ($via =~ /^::/){ + $via = $inline->get_api('pkg') . $via ; + } + if ($function =~ /^::/){ + $function = $inline->get_api('pkg') . $function ; + } + + return Inline::Java::Callback::ProcessCallback($inline, $via, $function, $cast_return, @args) ; + } + + croak "Malformed callback request from server: $resp" ; +} + + +sub ProcessCallback { + my $inline = shift ; + my $via = shift ; + my $function = shift ; + my $cast_return = shift ; + my @sargs = @_ ; + + my $list_ctx = 0 ; + if ($function =~ s/^\@//){ + $list_ctx = 1 ; + } + + my $pc = new Inline::Java::Protocol(undef, $inline) ; + my $thrown = 'false' ; + + my $ret = undef ; + my @ret = () ; + eval { + my @args = map { + my $a = $pc->DeserializeObject(0, $_) ; + $a ; + } @sargs ; + + no strict 'refs' ; + if ($via =~ /^(\d+)$/){ + # Call via object + my $id = $1 ; + Inline::Java::debug(2, "processing callback $id" . "->" . "$function(" . + join(", ", @args) . ")") ; + my $obj = Inline::Java::Callback::GetObject($id) ; + if ($list_ctx){ + @ret = $obj->$function(@args) ; + } + else{ + $ret = $obj->$function(@args) ; + } + } + elsif ($via ne 'null'){ + # Call via package + Inline::Java::debug(2, "processing callback $via" . "->" . "$function(" . + join(", ", @args) . ")") ; + if ($list_ctx){ + @ret = $via->$function(@args) ; + } + else{ + $ret = $via->$function(@args) ; + } + } + else { + # Straight call + Inline::Java::debug(2, "processing callback $function(" . + join(", ", @args) . ")") ; + if ($function !~ /::/){ + $function = 'main' . '::' . $function ; + } + if ($list_ctx){ + @ret = $function->(@args) ; + } + else{ + $ret = $function->(@args) ; + } + } + + if ($list_ctx){ + $ret = \@ret ; + } + } ; + if ($@){ + $ret = $@ ; + $thrown = 'true' ; + + if ((ref($ret))&&(! UNIVERSAL::isa($ret, "Inline::Java::Object"))){ + croak "Can't propagate non-Inline::Java reference exception ($ret) to Java" ; + } + } + + ($ret) = Inline::Java::Class::CastArgument($ret, $cast_return, $inline) ; + + # Here we must keep a reference to $ret or else it gets deleted + # before the id is returned to Java... + my $ref = $ret ; + + ($ret) = $pc->ValidateArgs([$ret], 1) ; + + return ("callback $thrown $ret", $ref) ; +} + + +sub GetObject { + my $id = shift ; + + my $obj = $OBJECTS{$id} ; + if (! defined($obj)){ + croak("Can't find object $id") ; + } + + return $obj ; +} + + +sub PutObject { + my $obj = shift ; + + my $id = $next_id ; + $next_id++ ; + + $OBJECTS{$id} = $obj ; + + return $id ; +} + + +sub DeleteObject { + my $id = shift ; + my $quiet = shift || 0 ; + + my $obj = delete $OBJECTS{$id} ; + if ((! $quiet)&&(! defined($obj))){ + croak("Can't find object $id") ; + } +} + + +sub ObjectCount { + return scalar(keys %OBJECTS) ; +} + + +sub __GetObjects { + return \%OBJECTS ; +} + + + +########## Utility methods used by Java to access Perl objects ################# + + +sub java_eval { + my $code = shift ; + + Inline::Java::debug(3, "evaling Perl code: $code") ; + my $ret = eval $code ; + if ($@){ + die($@) ; + } + + return $ret ; +} + + +sub java_require { + my $module = shift ; + my $is_file = shift ; + + if (! defined($is_file)){ + if (-e $module){ + $module = "\"$module\"" ; + } + } + + if ($is_file){ + $module = "\"$module\"" ; + } + + Inline::Java::debug(3, "requiring Perl module/file: $module") ; + return java_eval("require $module ;") ; +} + + +sub java_finalize { + my $id = shift ; + my $gc = shift ; + + Inline::Java::Callback::DeleteObject($id, $gc) ; +} + + +1 ; diff --git a/Java/Callback.pod b/Java/Callback.pod new file mode 100644 index 0000000..2106c57 --- /dev/null +++ b/Java/Callback.pod @@ -0,0 +1,406 @@ +=head1 NAME + +Inline::Java::Callback - Callback into Perl from Java. + +=head1 SYNOPSIS + +=for comment + + use Inline Java => <<'END' ; + import org.perl.inline.java.* ; + + class Pod_caller extends InlineJavaPerlCaller { + public Pod_caller() throws InlineJavaException { + } + + public String perl() + throws InlineJavaException, InlineJavaPerlException { + + return (String)CallPerlSub("main::perl", + new Object [] {}) ; + } + } + END + + my $pc = new Pod_caller() ; + print($pc->perl() . "\n") ; # prints perl + + sub perl { + return "perl" ; + } + +=for comment + + +=head1 DESCRIPTION + +C<Inline::Java::Callback> allows you to call Perl functions from Java. To +do this you need to create an C<org.perl.inline.java.InlinePerlCaller> +object. Here is a example of a typical use: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + import org.perl.inline.java.* ; + + class Pod_regexp extends InlineJavaPerlCaller { + public Pod_regexp() throws InlineJavaException { + } + + public boolean match(String target, String pattern) + throws InlineJavaException { + try { + String m = (String)CallPerlSub("main::regexp", + new Object [] {target, pattern}) ; + + if (m.equals("1")){ + return true ; + } + } + catch (InlineJavaPerlException pe){ + // $@ is in pe.GetObject() + } + + return false ; + } + } + END + + my $re = new Pod_regexp() ; + my $match = $re->match("Inline::Java", "^Inline") ; + print($match . "\n") ; # prints 1 + + sub regexp { + my $target = shift ; + my $pattern = shift ; + + return ($target =~ /$pattern/) ; + } + +=for comment + + +=head1 CALLBACK API + +Here are the various methods that one can use to call into +Perl: + +=over 4 + +=item public Object CallPerlSub(String sub, +Object args[], Class cast) +throws InlineJavaException, InlineJavaPerlException + +Calls the specified subroutine with the supplied arguments and tries +to create an object of type 'cast' with the result. + + /* Example */ + Integer sum = (Integer)CallPerlSub("main::add", new Object [] {new Integer(5), new Integer(3)}, Integer.class) ; + +=item public Object CallPerlStaticMethod(String pkg, String method, +Object args[], Class cast) +throws InlineJavaException, InlineJavaPerlException + +Calls the specified static package method (using the $pkg->$method() +notation) with the supplied arguments and tries to create an object +of type 'cast' with the result. + + /* Example */ + Integer sum = (Integer)CallPerlStaticMethod("main", "add", new Object [] {new Integer(5), new Integer(3)}, Integer.class) ; + +=item public Object eval(String code, Class cast) +throws InlineJavaPerlException, InlineJavaException + +Evaluates the given Perl code and tries to create an object +of type 'cast' with the result. + + /* Example */ + Integer sum = (Integer)eval("5 + 3", Integer.class) ; + +=item public Object require(String module_or_file) +throws InlineJavaPerlException, InlineJavaException + +Requires the specified module/file by using a heuristic (currently, +checks whether or not the file exists) and calling Perl's C<require> +function using the appropriate construct. + + /* Example */ + require("Someting") + +=item public Object require_file(String file) +throws InlineJavaPerlException, InlineJavaException + +Requires the specified file. + + /* Example */ + require_file("./my_stuff.pl") ; + +=item public Object require_module(String module) +throws InlineJavaPerlException, InlineJavaException + +Requires the specified module. + + /* Example */ + require_module("Data::Dumper") ; + +=back + +Note: For all CallPerl* and eval methods, the 'cast' parameter is optional +and defaults to 'String.class'. + +These methods can throw 2 types of exceptions: C<InlineJavaException> and +C<InlineJavaPerlException> (both of these belong to the C<org.perl.inline.java> +package). The former designates an internal C<Inline::Java> error and the +latter indicates that the Perl callback threw an exception (die() or croak()). +The value of $@ (this can be a scalar or any valid "Inline::Java" object) can +be retreived using the GetObject() method of the C<InlineJavaPerlException> +object (if you are certain that $@ was a Perl scalar, you can use the +GetString() method). + Z<> + + +=head1 CALLBACK CONTEXT + +By default, callback are executed in scalar context. However if you want to +call certain functions in list context, you must insert "@" in front of the +function name. The result will then be passed on to Java as an Array: + +=for comment + + use Inline Java => <<'END' ; + import org.perl.inline.java.* ; + + class Pod_Context { + static private String dummy[] = {} ; + + static public String [] get_list() + throws InlineJavaException, InlineJavaPerlException { + InlineJavaPerlCaller pc = new InlineJavaPerlCaller() ; + return (String [])pc.CallPerlSub("@main::list", + null, dummy.getClass()) ; + } + } + END + + sub list { + return ('a', 'b', 'c') ; + } + + print(Pod_Context->get_list()->[1] . "\n") ; # prints b + +=for comment + +Note: When calling a Perl function that returns a list or array, you will +need to pass the Class object for the expected array type (in this case +String []). Since these Class objects are difficult to access for array +types, the easiest way to do this is to create a dummy array of the desired +type and call the getClass() method on that object (as seen above). + Z<> + + +=head1 CALLBACK LOOPS + +It is now possible to use callbacks from different Java threads. One of the +big advantages of this is that you can now handle, for example, SWING events +in Perl. Here's an example: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + import org.perl.inline.java.* ; + import javax.swing.* ; + import java.awt.event.* ; + + class Pod_Button extends InlineJavaPerlCaller + implements ActionListener { + JFrame frame = null ; + + public Pod_Button() throws InlineJavaException { + frame = new JFrame("Pod_Button") ; + frame.setSize(100,100) ; + JButton button = new JButton("Click Me!") ; + frame.getContentPane().add(button) ; + button.addActionListener(this) ; + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE) ; + frame.show() ; + } + + public void actionPerformed(ActionEvent e){ + try { + CallPerlSub("main::button_pressed", new Object [] {}) ; + } + catch (InlineJavaPerlException pe){ + // $@ is in pe.GetObject() + } + catch (InlineJavaException pe) { + pe.printStackTrace() ; + } + } + + public void close(){ + frame.dispose() ; + frame.hide() ; + frame = null ; + } + + public void quit(){ + System.exit(0) ; + } + } + END + + my $b = new Pod_Button() ; + $b->StartCallbackLoop() ; + $b->close() ; + + # Maybe do some other stuff + + exit() ; # in client-server mode, optional + $b->quit() ; # in JNI mode + + sub button_pressed { + print('click!' . "\n") ; # prints click! + $b->StopCallbackLoop() ; + } + +=for comment + +The StartCallbackLoop method can be called on any +C<org.perl.inline.java.InlineJavaPerlCaller> object and will block the +current thread and allow the reception of callbacks through +any InlineJavaPerlCaller that has been created by the same (current) thread. +The only way to interrupt such a StartCallbackLoop method is to call the +StopCallbackLoop method on any C<org.perl.inline.java.InlineJavaPerlCaller> +object that has been created by that same thread. + +Also, only threads that communicate with Perl through C<Inline::Java> are allowed +to create C<org.perl.inline.java.InlineJavaPerlCaller> objects and invoke their +StartCallbackLoop / StopCallbackLoop methods. + Z<> + + +=head1 SELECT-STYLE CALLBACK LOOPS + +The disadvantage with the type of callback loop presented in the previous +section is that the main portion of the Perl program is completely blocked +while waiting for callbacks. In version 0.51 a new API for callback loops +was introduced, allowing for callbacks to be processed much in the same +fashion one uses select(2) to read data from a filehandle. Here's an +example: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + import org.perl.inline.java.* ; + import javax.swing.* ; + import java.awt.event.* ; + + class Pod_Button extends InlineJavaPerlCaller + implements ActionListener { + JFrame frame = null ; + + public Pod_Button() throws InlineJavaException { + frame = new JFrame("Pod_Button") ; + frame.setSize(100,100) ; + JButton button = new JButton("Click Me!") ; + frame.getContentPane().add(button) ; + button.addActionListener(this) ; + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE) ; + frame.show() ; + } + + public void actionPerformed(ActionEvent e){ + try { + CallPerlSub("main::button_pressed", new Object [] {}) ; + } + catch (InlineJavaPerlException pe){ + // $@ is in pe.GetObject() + } + catch (InlineJavaException pe) { + pe.printStackTrace() ; + } + } + + public void close(){ + frame.dispose() ; + frame.hide() ; + frame = null ; + } + + public void quit(){ + System.exit(0) ; + } + } + END + + my $b = new Pod_Button() ; + $b->OpenCallbackStream() ; + while ((my $rc = $b->WaitForCallback(5)) > -1){ + if ($rc > 0){ + # A callback is pending, we must process it. + $b->ProcessNextCallback() ; + } + else { + # A timeout has occured after, in this case, 5 secs. + print "5 seconds have passed, still waiting for callback...\n" ; + # Maybe do some other stuff + } + } + $b->close() ; + + # Maybe do some other stuff + + exit() ; # in client-server mode, optional + $b->quit() ; # in JNI mode + + sub button_pressed { + print('click!' . "\n") ; # prints click! + $b->CloseCallbackStream() ; + } + +=for comment + +The StartCallbackStream method can be called on any InlineJavaPerlCaller object +to initialize a channel to receive callbacks. The WaitForCallback method can +then be called with a float timeout value (-1 means wait forever, 0 means return +immediately). The WaitForCallback method can return: + + rc > 0, indicating that rc callbacks are waiting to be processed + rc == 0, indicating that a timeout has occured and no callbacks are waiting + rc == -1, indicating that the callback stream has been closed + +The callback stream can be closed by calling CloseCallbackStream, which works +similarly to the StopCallbackLoop method used in the previous section. + +Also, the restrictions regarding thread communication stated in the previous +section are valid in this case as well. + Z<> + + +=head1 SEE ALSO + +L<Inline::Java>, L<Inline::Java::PerlNatives>, L<Inline::Java::PerlInterpreter>. + Z<> + + +=head1 AUTHOR + +Patrick LeBoutillier <patl@cpan.org> is the author of Inline::Java. + +Brian Ingerson <ingy@cpan.org> is the author of Inline. + Z<> + + +=head1 COPYRIGHT + +Copyright (c) 2001-2004, Patrick LeBoutillier. + +All Rights Reserved. This module is free software. It may be used, +redistributed and/or modified under the terms of the Perl Artistic +License. See http://www.perl.com/perl/misc/Artistic.html for more +details. + +=cut diff --git a/Java/Class.pm b/Java/Class.pm new file mode 100644 index 0000000..0558885 --- /dev/null +++ b/Java/Class.pm @@ -0,0 +1,511 @@ +package Inline::Java::Class ; + +use strict ; +use Carp ; + +$Inline::Java::Class::VERSION = '0.53' ; + +$Inline::Java::Class::MAX_SCORE = 10 ; + +# There is no use supporting exponent notation for integer types since +# Jave does not support it without casting. +my $INT_RE = '^[+-]?\d+$' ; +my $FLOAT_RE = '^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$' ; + +my $RANGE = { + 'java.lang.Byte' => { + REGEXP => $INT_RE, + MAX => 127, + MIN => -128, + }, + 'java.lang.Short' => { + REGEXP => $INT_RE, + MAX => 32767, + MIN => -32768, + }, + 'java.lang.Integer' => { + REGEXP => $INT_RE, + MAX => 2147483647, + MIN => -2147483648, + }, + 'java.lang.Float' => { + REGEXP => $FLOAT_RE, + MAX => 3.4028235e38, + MIN => -3.4028235e38, + # POS_MIN => 1.4e-45, + # NEG_MAX => -1.4e-45, + }, + 'java.lang.Long' => { + REGEXP => $INT_RE, + # MAX => 9223372036854775807, + # MIN => -9223372036854775808, + }, + 'java.lang.Double' => { + REGEXP => $FLOAT_RE, + # MAX => 1.79e308, + # MIN => -1.79e308, + # POS_MIN => 4.9e-324, + # NEG_MAX => -4.9e-324, + }, +} ; +$RANGE->{byte} = $RANGE->{'java.lang.Byte'} ; +$RANGE->{short} = $RANGE->{'java.lang.Short'} ; +$RANGE->{'int'} = $RANGE->{'java.lang.Integer'} ; +$RANGE->{long} = $RANGE->{'java.lang.Long'} ; +$RANGE->{float} = $RANGE->{'java.lang.Float'} ; +$RANGE->{double} = $RANGE->{'java.lang.Double'} ; + +# java.lang.Number support. We allow the widest range +# i.e. Double +$RANGE->{'java.lang.Number'} = $RANGE->{'java.lang.Double'} ; + + +my %numeric_classes = map {($_ => 1)} qw( + java.lang.Byte + java.lang.Short + java.lang.Integer + java.lang.Long + java.lang.Float + java.lang.Double + java.lang.Number + byte + short + int + long + float + double +) ; + + +my %double_classes = map {($_ => 1)} qw( + java.lang.Double + double +) ; + +my %string_classes = map {($_ => 1)} qw( + java.lang.String + java.lang.StringBuffer + java.lang.CharSequence +) ; + +my %char_classes = map {($_ => 1)} qw( + java.lang.Character + char +) ; + +my %bool_classes = map {($_ => 1)} qw( + java.lang.Boolean + boolean +) ; + + +# This method makes sure that the class we are asking for +# has the correct form for a Java class. +sub ValidateClass { + my $class = shift ; + + my $ret = ValidateClassSplit($class) ; + + return $ret ; +} + + +my $class_name_regexp = '([\w$]+)(((\.([\w$]+))+)?)' ; +my $class_regexp1 = qr/^($class_name_regexp)()()()$/o ; +my $class_regexp2 = qr/^(\[+)([BCDFIJSZ])()()$/o ; +my $class_regexp3 = qr/^(\[+)([L])($class_name_regexp)(;)$/o ; +sub ValidateClassSplit { + my $class = shift ; + + if (($class =~ $class_regexp1)|| + ($class =~ $class_regexp2)|| + ($class =~ $class_regexp3)){ + return (wantarray ? ($1, $2, $3, $4) : $class) ; + } + + croak "Invalid Java class name $class" ; +} + + +sub CastArguments { + my $args = shift ; + my $proto = shift ; + my $inline = shift ; + + Inline::Java::debug_obj($args) ; + Inline::Java::debug_obj($proto) ; + + my $nb_args = scalar(@{$args}) ; + if ($nb_args != scalar(@{$proto})){ + croak "Wrong number of arguments" ; + } + + my $ret = [] ; + my $score = 0 ; + for (my $i = 0 ; $i < $nb_args ; $i++){ + my $arg = $args->[$i] ; + my $pro = $proto->[$i] ; + my @r = CastArgument($arg, $pro, $inline) ; + $ret->[$i] = $r[0] ; + + $score += $r[1] ; + } + + return ($ret, $score) ; +} + + +sub CastArgument { + my $arg = shift ; + my $proto = shift ; + my $inline = shift ; + + ValidateClass($proto) ; + + my $arg_ori = $arg ; + my $proto_ori = $proto ; + + my $array_score = 0 ; + + my @ret = eval { + my $array_type = undef ; + if ((defined($arg))&&(UNIVERSAL::isa($arg, "Inline::Java::Class::Coerce"))){ + my $v = $arg->__get_value() ; + $proto = $arg->__get_type() ; + $array_type = $arg->__get_array_type() ; + $arg = $v ; + } + + if ((ClassIsReference($proto))&& + (defined($arg))&& + (! UNIVERSAL::isa($arg, "Inline::Java::Object"))){ + # Here we allow scalars to be passed in place of java.lang.Object + # They will wrapped on the Java side. + if (UNIVERSAL::isa($arg, "ARRAY")){ + if (! UNIVERSAL::isa($arg, "Inline::Java::Array")){ + my $an = Inline::Java::Array::Normalizer->new($inline, $array_type || $proto, $arg) ; + $array_score = $an->{score} ; + my $flat = $an->FlattenArray() ; + + # We need to create the array on the Java side, and then grab + # the returned object. + my $obj = Inline::Java::Object->__new($array_type || $proto, $inline, -1, $flat->[0], $flat->[1]) ; + $arg = new Inline::Java::Array($obj) ; + } + else{ + Inline::Java::debug(4, "argument is already an Inline::Java array") ; + } + } + else{ + if (ref($arg)){ + # We got some other type of ref... + if ($arg !~ /^(.*?)=/){ + # We do not have a blessed reference, so ... + croak "Can't convert $arg to object $proto" ; + } + } + else { + # Here we got a scalar + # Here we allow scalars to be passed in place of java.lang.Object + # They will wrapped on the Java side. + if ($proto ne "java.lang.Object"){ + croak "Can't convert $arg to object $proto" ; + } + } + } + } + if ((ClassIsPrimitive($proto))&&(ref($arg))){ + croak "Can't convert $arg to primitive $proto" ; + } + + if (ClassIsNumeric($proto)){ + if (! defined($arg)){ + # undef gets lowest score since it can be passed + # as anything + return (0, 1) ; + } + my $re = $RANGE->{$proto}->{REGEXP} ; + my $min = $RANGE->{$proto}->{MIN} ; + my $max = $RANGE->{$proto}->{MAX} ; + Inline::Java::debug(4, + "min = " . ($min || '') . ", " . + "max = " . ($max || '') . ", " . + "val = $arg") ; + if ($arg =~ /$re/){ + if (((! defined($min))||($arg >= $min))&& + ((! defined($max))||($arg <= $max))){ + # number is a pretty precise match, but it's still + # guessing amongst the numeric types + my $points = 5.5 ; + if (($inline->get_java_config('NATIVE_DOUBLES'))&&(ClassIsDouble($proto))){ + # We want to send the actual double bytes to Java + my $bytes = pack("d", $arg) ; + $arg = bless(\$bytes, 'Inline::Java::double') ; + return ($arg, $points) ; + } + else { + return ($arg, $points) ; + } + } + croak "$arg out of range for type $proto" ; + } + croak "Can't convert $arg to $proto" ; + } + elsif (ClassIsChar($proto)){ + if (! defined($arg)){ + # undef gets lowest score since it can be passed + # as anything + return ("\0", 1) ; + } + if (length($arg) == 1){ + # char is a pretty precise match + return ($arg, 5) ; + } + croak "Can't convert $arg to $proto" ; + } + elsif (ClassIsBool($proto)){ + if (! defined($arg)){ + # undef gets lowest score since it can be passed + # as anything + return (0, 1) ; + } + elsif (! $arg){ + # bool gets lowest score since anything is a bool + return (0, 1) ; + } + else{ + # bool gets lowest score since anything is a bool + return (1, 1) ; + } + } + elsif (ClassIsString($proto)){ + if (! defined($arg)){ + # undef gets lowest score since it can be passed + # as anything + return (undef, 1) ; + } + # string get almost lowest score since anything can match it + # except objects + if ($proto eq "java.lang.StringBuffer"){ + # in case we have both protos, we want to give String + # the advantage + return ($arg, 1.75) ; + } + return ($arg, 2) ; + } + else{ + if (! defined($arg)){ + # undef gets lowest score since it can be passed + # as anything + return ($arg, 1) ; + } + + # Here the prototype calls for an object of type $proto + # We must ask Java if our object extends $proto + if (ref($arg)){ + if ((UNIVERSAL::isa($arg, "Inline::Java::Object"))||(UNIVERSAL::isa($arg, "Inline::Java::Array"))){ + my ($msg, $score) = $arg->__isa($proto) ; + if ($msg){ + croak $msg ; + } + Inline::Java::debug(3, "$arg is a $proto") ; + + # a matching object, pretty good match, except if proto + # is java.lang.Object + if ($proto eq "java.lang.Object"){ + return ($arg, 1) ; + } + + # Here we deduce points the more our argument is "far" + # from the prototype. + if (! UNIVERSAL::isa($arg, "Inline::Java::Array")){ + return ($arg, 7 - ($score * 0.01)) ; + } + else{ + # We need to keep the array score somewhere... + return ($arg, $array_score) ; + } + } + else { + # We want to send a Perl object to the Java side. + my $ijp = new Inline::Java::Protocol(undef, $inline) ; + my $score = $ijp->__ISA('org.perl.inline.java.InlineJavaPerlObject', $proto) ; + if ($score == -1){ + croak "$proto is not a kind of org.perl.inline.java.InlineJavaPerlObject" ; + } + + Inline::Java::debug(3, "$arg is a $proto") ; + + # a matching object, pretty good match, except if proto + # is java.lang.Object + if ($proto eq "java.lang.Object"){ + return ($arg, 1) ; + } + else{ + return ($arg, 7 - ($score * 0.01)) ; + } + } + } + + # Here we are passing a scalar as an object, this is pretty + # vague as well + return ($arg, 1) ; + } + } ; + die("$@\n") if $@ ; + + if ((defined($arg_ori))&&(UNIVERSAL::isa($arg_ori, "Inline::Java::Class::Coerce"))){ + # It seems we had casted the variable to a specific type + if ($arg_ori->__matches($proto_ori)){ + Inline::Java::debug(3, "type coerce match!") ; + $ret[1] = $Inline::Java::Class::MAX_SCORE ; + } + else{ + # We have coerced to something that doesn't exactly match + # any of the available types. + # For now we don't allow this. + croak "Coerce ($proto) doesn't exactly match prototype ($proto_ori)" ; + } + } + + return @ret ; +} + + +sub IsMaxArgumentsScore { + my $args = shift ; + my $score = shift ; + + if ((scalar(@{$args}) * 10) == $score){ + return 1 ; + } + + return 0 ; +} + + +sub ClassIsNumeric { + my $class = shift ; + + return $numeric_classes{$class} ; +} + + +sub ClassIsDouble { + my $class = shift ; + + return $double_classes{$class} ; +} + + +sub ClassIsString { + my $class = shift ; + + return $string_classes{$class} ; +} + + +sub ClassIsChar { + my $class = shift ; + + return $char_classes{$class} ; +} + + +sub ClassIsBool { + my $class = shift ; + + return $bool_classes{$class} ; +} + + +sub ClassIsPrimitive { + my $class = shift ; + + if ((ClassIsNumeric($class))||(ClassIsString($class))||(ClassIsChar($class))||(ClassIsBool($class))){ + return 1 ; + } + + return 0 ; +} + + +sub ClassIsReference { + my $class = shift ; + + if (ClassIsPrimitive($class)){ + return 0 ; + } + + return 1 ; +} + + +sub ClassIsArray { + my $class = shift ; + + if ((ClassIsReference($class))&&($class =~ /^(\[+)(.*)$/)){ + return 1 ; + } + + return 0 ; +} + + + +######################## Inline::Java::Class::Coerce ######################## +package Inline::Java::Class::Coerce ; + + +use Carp ; + +sub new { + my $class = shift ; + my $type = shift ; + my $value = shift ; + my $array_type = shift ; + + if (UNIVERSAL::isa($value, "Inline::Java::Class::Coerce")){ + # This allows chaining + $value = $value->get_value() ; + } + + my $this = {} ; + $this->{cast} = Inline::Java::Class::ValidateClass($type) ; + $this->{value} = $value ; + $this->{array_type} = $array_type ; + + bless($this, $class) ; + return $this ; +} + + +sub __get_value { + my $this = shift ; + + return $this->{value} ; +} + + +sub __get_type { + my $this = shift ; + + return $this->{cast} ; +} + +sub __get_array_type { + my $this = shift ; + + return $this->{array_type} ; +} + + +sub __matches { + my $this = shift ; + my $proto = shift ; + + return ($proto eq $this->{cast}) ; +} + + +1 ; diff --git a/Java/Handle.pm b/Java/Handle.pm new file mode 100644 index 0000000..4bea9bc --- /dev/null +++ b/Java/Handle.pm @@ -0,0 +1,269 @@ +package Inline::Java::Handle ; +@Inline::Java::Handle::ISA = qw(Inline::Java::Handle::Tie) ; + +use strict ; +use Symbol ; +use Carp ; + +$Inline::Java::Handle::VERSION = '0.53' ; + + +# Here we store as keys the knots and as values our blessed objects +my $OBJECTS = {} ; + + +sub new { + my $class = shift ; + my $object = shift ; + + my $fh = gensym() ; + my $knot = tie *{$fh}, $class ; + my $this = bless ($fh, $class) ; + + $OBJECTS->{$knot} = $object ; + + Inline::Java::debug(5, "this = '$this'") ; + Inline::Java::debug(5, "knot = '$knot'") ; + + return $this ; +} + + +sub __get_object { + my $this = shift ; + + my $knot = tied $this || $this ; + + my $ref = $OBJECTS->{$knot} ; + if (! defined($ref)){ + croak "Unknown Java handle reference '$knot'" ; + } + + return $ref ; +} + + +sub __isa { + my $this = shift ; + my $proto = shift ; + + return $this->__get_object()->__isa($proto) ; +} + + +sub __read { + my $this = shift ; + my ($buf, $len, $offset) = @_ ; + + my $obj = $this->__get_object() ; + + my $ret = undef ; + eval { + my $str = $obj->__get_private()->{proto}->ReadFromJavaHandle($len) ; + $len = length($str) ; + if ($len > 0){ + substr($buf, $offset, $len) = $str ; + $_[0] = $buf ; + $ret = $len ; + } + } ; + croak $@ if $@ ; + + return $ret ; +} + + +sub __readline { + my $this = shift ; + + my $obj = $this->__get_object() ; + + my $ret = undef ; + eval { + $ret = $obj->__get_private()->{proto}->ReadLineFromJavaHandle() ; + } ; + croak $@ if $@ ; + + return $ret ; +} + + +sub __write { + my $this = shift ; + my $buf = shift ; + my $len = shift ; + my $offset = shift ; + + my $obj = $this->__get_object() ; + + my $ret = -1 ; + eval { + my $len = $obj->__get_private()->{proto}->WriteToJavaHandle(substr($buf, $offset, $len)) ; + $ret = $len ; + } ; + croak $@ if $@ ; +} + + +sub __eof { + my $this = shift ; +} + + +sub __close { + my $this = shift ; + + my $obj = $this->__get_object() ; + + my $ret = undef ; + { + local $@ ; + eval { + $ret = $obj->__get_private()->{proto}->CloseJavaHandle() ; + $obj->__get_private()->{closed} = 1 ; + } ; + croak $@ if $@ ; + } + + return $ret ; +} + + + +sub AUTOLOAD { + my $this = shift ; + my @args = @_ ; + + use vars qw($AUTOLOAD) ; + my $func_name = $AUTOLOAD ; + # Strip package from $func_name, Java will take of finding the correct + # method. + $func_name =~ s/^(.*)::// ; + + croak "Can't call method '$func_name' on Java handles" ; +} + + +sub DESTROY { + my $this = shift ; + + + my $knot = tied *{$this} ; + if (! $knot){ + Inline::Java::debug(4, "destroying Inline::Java::Handle::Tie") ; + + my $obj = $this->__get_object() ; + if (! $obj->__get_private()->{closed}){ + $this->__close() ; + } + + $OBJECTS->{$this} = undef ; + } + else { + Inline::Java::debug(4, "destroying Inline::Java::Handle") ; + } +} + + + +######################## Handle methods ######################## +package Inline::Java::Handle::Tie ; +@Inline::Java::Handle::Tie::ISA = qw(Tie::StdHandle) ; + + +use Tie::Handle ; +use Carp ; + + +sub TIEHANDLE { + my $class = shift ; + my $jclass = shift ; + + return $class->SUPER::TIEHANDLE(@_) ; +} + + +sub READ { + my $this = shift ; + my ($buf, $len, $offset) = @_ ; + + my $ret = $this->__read($buf, $len, $offset) ; + $_[0] = $buf ; + + return $ret ; +} + + +sub READLINE { + my $this = shift ; + + return $this->__readline() ; +} + + +sub WRITE { + my $this = shift ; + my $buf = shift ; + my $len = shift ; + my $offset = shift ; + + return $this->__write($buf, $len, $offset) ; +} + + +sub BINMODE { + my $this = shift ; + + croak "Operation BINMODE not supported on Java handle" ; +} + + +sub OPEN { + my $this = shift ; + + croak "Operation OPEN not supported on Java handle" ; +} + + +sub TELL { + my $this = shift ; + + croak "Operation TELL not supported on Java handle" ; +} + + +sub FILENO { + my $this = shift ; + + croak "Operation FILENO not supported on Java handle" ; +} + + +sub SEEK { + my $this = shift ; + + croak "Operation SEEK not supported on Java handle" ; +} + + +sub EOF { + my $this = shift ; + + return $this->__eof() ; +} + + +sub CLOSE { + my $this = shift ; + + return $this->__close() ; +} + + +sub DESTROY { + my $this = shift ; +} + + +1 ; + diff --git a/Java/JNI.pm b/Java/JNI.pm new file mode 100644 index 0000000..9384401 --- /dev/null +++ b/Java/JNI.pm @@ -0,0 +1,41 @@ +package Inline::Java::JNI ; +@Inline::Java::JNI::ISA = qw(DynaLoader) ; + + +use strict ; + +$Inline::Java::JNI::VERSION = '0.53' ; + +use DynaLoader ; +use Carp ; +use File::Basename ; + + +if ($^O eq 'solaris'){ + load_lib('-lthread') ; +} + + +sub load_lib { + my $l = shift ; + my $lib = (DynaLoader::dl_findfile($l))[0] ; + + if ((! $lib)||(! defined(DynaLoader::dl_load_file($lib, 0x01)))){ + carp("Couldn't find or load $l.") ; + } +} + + +# A place to attach the Inline object that is currently in Java land +$Inline::Java::JNI::INLINE_HOOK = undef ; + + +eval { + Inline::Java::JNI->bootstrap($Inline::Java::JNI::VERSION) ; +} ; +if ($@){ + croak "Can't load JNI module. Did you build it at install time?\nError: $@" ; +} + + +1 ; diff --git a/Java/JNI.xs b/Java/JNI.xs new file mode 100644 index 0000000..cc359d7 --- /dev/null +++ b/Java/JNI.xs @@ -0,0 +1,311 @@ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#ifdef __CYGWIN__ + #include "w32api/basetyps.h" +#endif + +/* Include the JNI header file */ +#include "jni.h" + + +/* JNI structure */ +typedef struct { + JavaVM *jvm ; + jclass ijs_class ; + jobject ijs ; + jmethodID jni_main_mid ; + jmethodID process_command_mid ; + jint debug ; + int embedded ; + int native_doubles ; + int destroyed ; +} InlineJavaJNIVM ; + + +void shutdown_JVM(InlineJavaJNIVM *this){ + if ((! this->embedded)&&(! this->destroyed)){ + (*(this->jvm))->DestroyJavaVM(this->jvm) ; + this->destroyed = 1 ; + } +} + + +JNIEnv *get_env(InlineJavaJNIVM *this){ + JNIEnv *env ; + + (*(this->jvm))->AttachCurrentThread(this->jvm, ((void **)&env), NULL) ; + + return env ; +} + + +/* + This is only used to trap exceptions from Perl. +*/ +void check_exception_from_perl(JNIEnv *env, char *msg){ + if ((*(env))->ExceptionCheck(env)){ + (*(env))->ExceptionDescribe(env) ; + (*(env))->ExceptionClear(env) ; + croak(msg) ; + } +} + + +void throw_ije(JNIEnv *env, char *msg){ + jclass ije ; + + ije = (*(env))->FindClass(env, "org/perl/inline/java/InlineJavaException") ; + if ((*(env))->ExceptionCheck(env)){ + (*(env))->ExceptionDescribe(env) ; + (*(env))->ExceptionClear(env) ; + (*(env))->FatalError(env, "Can't find class InlineJavaException: exiting...") ; + } + (*(env))->ThrowNew(env, ije, msg) ; +} + + +jstring JNICALL jni_callback(JNIEnv *env, jobject obj, jstring cmd){ + dSP ; + jstring resp ; + char *c = (char *)((*(env))->GetStringUTFChars(env, cmd, NULL)) ; + char *r = NULL ; + int count = 0 ; + SV *hook = NULL ; + char msg[128] ; + + ENTER ; + SAVETMPS ; + + PUSHMARK(SP) ; + XPUSHs(&PL_sv_undef) ; + XPUSHs(sv_2mortal(newSVpv(c, 0))) ; + PUTBACK ; + + (*(env))->ReleaseStringUTFChars(env, cmd, c) ; + count = perl_call_pv("Inline::Java::Callback::InterceptCallback", + G_ARRAY|G_EVAL) ; + + SPAGAIN ; + + /* Check the eval */ + if (SvTRUE(ERRSV)){ + STRLEN n_a ; + throw_ije(env, SvPV(ERRSV, n_a)) ; + } + else{ + if (count != 2){ + sprintf(msg, "Invalid return value from Inline::Java::Callback::InterceptCallback: %d", + count) ; + throw_ije(env, msg) ; + } + } + + /* + The first thing to pop is a reference to the returned object, + which we must keep around long enough so that it is not deleted + before control gets back to Java. This is because this object + may be returned be the callback, and when it gets back to Java + it will already be deleted. + */ + hook = perl_get_sv("Inline::Java::Callback::OBJECT_HOOK", FALSE) ; + sv_setsv(hook, POPs) ; + + r = (char *)POPp ; + resp = (*(env))->NewStringUTF(env, r) ; + + PUTBACK ; + FREETMPS ; + LEAVE ; + + return resp ; +} + + + +/*****************************************************************************/ + + + +MODULE = Inline::Java::JNI PACKAGE = Inline::Java::JNI + + +PROTOTYPES: DISABLE + + +InlineJavaJNIVM * +new(CLASS, classpath, args, embedded, debug, native_doubles) + char * CLASS + char * classpath + SV * args + int embedded + int debug + int native_doubles + + PREINIT: + JavaVMInitArgs vm_args ; + JavaVMOption *options ; + JNIEnv *env ; + JNINativeMethod nm ; + jint res ; + char *cp ; + int args_len ; + int i ; + SV ** val = NULL ; + STRLEN n_a ; + + CODE: + args = SvRV(args) ; + RETVAL = (InlineJavaJNIVM *)safemalloc(sizeof(InlineJavaJNIVM)) ; + if (RETVAL == NULL){ + croak("Can't create InlineJavaJNIVM") ; + } + RETVAL->ijs = NULL ; + RETVAL->debug = debug ; + RETVAL->embedded = embedded ; + RETVAL->native_doubles = native_doubles ; + RETVAL->destroyed = 0 ; + + /* Figure out the length of the args array */ + args_len = av_len((AV *)args) + 1 ; + vm_args.version = JNI_VERSION_1_2 ; + + options = (JavaVMOption *)malloc((2 + args_len) * sizeof(JavaVMOption)) ; + vm_args.options = options ; + vm_args.nOptions = 0 ; + vm_args.ignoreUnrecognized = JNI_FALSE ; + + options[vm_args.nOptions++].optionString = + ((RETVAL->debug > 5) ? "-verbose" : "-verbose:") ; + cp = (char *)malloc((strlen(classpath) + 32) * sizeof(char)) ; + sprintf(cp, "-Djava.class.path=%s", classpath) ; + options[vm_args.nOptions++].optionString = cp ; + + for (i = 0 ; i < args_len ; i++){ + val = av_fetch((AV *)args, i, 0) ; + if (val != NULL){ + options[vm_args.nOptions++].optionString = SvPV(*val, n_a) ; + } + } + + /* Embedded patch and idea by Doug MacEachern */ + if (RETVAL->embedded) { + /* We are already inside a JVM */ + jint n = 0 ; + + res = JNI_GetCreatedJavaVMs(&(RETVAL->jvm), 1, &n) ; + if (n <= 0) { + /* res == 0 even if no JVMs are alive */ + res = -1; + } + if (res < 0) { + croak("Can't find any created Java JVMs") ; + } + + env = get_env(RETVAL) ; + } + else { + /* Create the Java VM */ + res = JNI_CreateJavaVM(&(RETVAL->jvm), (void **)&(env), &vm_args) ; + if (res < 0) { + croak("Can't create Java JVM using JNI") ; + } + } + + free(options) ; + free(cp) ; + + + /* Load the classes that we will use */ + RETVAL->ijs_class = (*(env))->FindClass(env, "org/perl/inline/java/InlineJavaServer") ; + check_exception_from_perl(env, "Can't find class InlineJavaServer") ; + RETVAL->ijs_class = (*(env))->NewGlobalRef(env, RETVAL->ijs_class) ; + + /* Get the method ids that are needed later */ + RETVAL->jni_main_mid = (*(env))->GetStaticMethodID(env, RETVAL->ijs_class, "jni_main", + "(IZ)Lorg/perl/inline/java/InlineJavaServer;") ; + check_exception_from_perl(env, "Can't find method jni_main in class InlineJavaServer") ; + RETVAL->process_command_mid = (*(env))->GetMethodID(env, RETVAL->ijs_class, "ProcessCommand", + "(Ljava/lang/String;)Ljava/lang/String;") ; + check_exception_from_perl(env, "Can't find method ProcessCommand in class InlineJavaServer") ; + + /* Register the callback function */ + nm.name = "jni_callback" ; + nm.signature = "(Ljava/lang/String;)Ljava/lang/String;" ; + nm.fnPtr = jni_callback ; + (*(env))->RegisterNatives(env, RETVAL->ijs_class, &nm, 1) ; + check_exception_from_perl(env, "Can't register method jni_callback in class InlineJavaServer") ; + + OUTPUT: + RETVAL + + + +void +shutdown(this) + InlineJavaJNIVM * this + + CODE: + shutdown_JVM(this) ; + + + +void +DESTROY(this) + InlineJavaJNIVM * this + + CODE: + shutdown_JVM(this) ; + safefree(this) ; + + + +void +create_ijs(this) + InlineJavaJNIVM * this + + PREINIT: + JNIEnv *env ; + + CODE: + env = get_env(this) ; + this->ijs = (*(env))->CallStaticObjectMethod(env, this->ijs_class, this->jni_main_mid, this->debug, this->native_doubles) ; + check_exception_from_perl(env, "Can't call jni_main in class InlineJavaServer") ; + this->ijs = (*(env))->NewGlobalRef(env, this->ijs) ; + + + +char * +process_command(this, data) + InlineJavaJNIVM * this + char * data + + PREINIT: + JNIEnv *env ; + jstring cmd ; + jstring resp ; + SV *hook = NULL ; + + CODE: + env = get_env(this) ; + cmd = (*(env))->NewStringUTF(env, data) ; + check_exception_from_perl(env, "Can't create java.lang.String") ; + + resp = (*(env))->CallObjectMethod(env, this->ijs, this->process_command_mid, cmd) ; + /* Thanks Dave Blob for spotting this. This is necessary since this code never really returns to Java + It simply calls into Java and comes back. */ + (*(env))->DeleteLocalRef(env, cmd); + check_exception_from_perl(env, "Can't call ProcessCommand in class InlineJavaServer") ; + + hook = perl_get_sv("Inline::Java::Callback::OBJECT_HOOK", FALSE) ; + sv_setsv(hook, &PL_sv_undef) ; + + RETVAL = (char *)((*(env))->GetStringUTFChars(env, resp, NULL)) ; + + OUTPUT: + RETVAL + + CLEANUP: + (*(env))->DeleteLocalRef(env, resp) ; + (*(env))->ReleaseStringUTFChars(env, resp, RETVAL) ; diff --git a/Java/JVM.pm b/Java/JVM.pm new file mode 100644 index 0000000..3797cd5 --- /dev/null +++ b/Java/JVM.pm @@ -0,0 +1,449 @@ +package Inline::Java::JVM ; + + +use strict ; +use Carp ; +use IO::File ; +use IPC::Open3 ; +use IO::Socket ; +use Text::ParseWords ; +use Inline::Java::Portable ; + +$Inline::Java::JVM::VERSION = '0.53' ; + +my %SIGS = () ; + +my @SIG_LIST = ('HUP', 'INT', 'PIPE', 'TERM') ; + +sub new { + my $class = shift ; + my $o = shift ; + + my $this = {} ; + bless($this, $class) ; + + foreach my $sig (@SIG_LIST){ + local $SIG{__WARN__} = sub {} ; + if (exists($SIG{$sig})){ + $SIGS{$sig} = $SIG{$sig} ; + } + } + + $this->{socket} = undef ; + $this->{JNI} = undef ; + $this->{embedded} = $o->get_java_config('EMBEDDED_JNI') ; + $this->{owner} = 1 ; + $this->{destroyed} = 0 ; + $this->{private} = $o->get_java_config('PRIVATE') ; + $this->{debugger} = $o->get_java_config('DEBUGGER') ; + + if ($this->{embedded}){ + Inline::Java::debug(1, "using embedded JVM...") ; + } + else{ + Inline::Java::debug(1, "starting JVM...") ; + } + + my $args = $o->get_java_config('EXTRA_JAVA_ARGS') ; + if ($o->get_java_config('JNI')){ + Inline::Java::debug(1, "JNI mode") ; + + # Split args and remove quotes + my @args = map {s/(['"])(.*)\1/$2/ ; $_} + parse_line('\s+', 1, $args) ; + my $jni = new Inline::Java::JNI( + $ENV{CLASSPATH} || '', + \@args, + $this->{embedded}, + Inline::Java::get_DEBUG(), + $o->get_java_config('NATIVE_DOUBLES'), + ) ; + $jni->create_ijs() ; + + $this->{JNI} = $jni ; + } + else { + Inline::Java::debug(1, "client/server mode") ; + + my $debug = Inline::Java::get_DEBUG() ; + + $this->{shared} = $o->get_java_config('SHARED_JVM') ; + $this->{start_jvm} = $o->get_java_config('START_JVM') ; + $this->{port} = $o->get_java_config('PORT') ; + $this->{host} = $o->get_java_config('HOST') ; + + # Used to limit the bind of the JVM server + $this->{'bind'} = $o->get_java_config('BIND') ; + + # Grab the next free port number and release it. + if ((! $this->{shared})&&($this->{port} < 0)){ + if (Inline::Java::Portable::portable("GOT_NEXT_FREE_PORT")){ + my $sock = IO::Socket::INET->new( + Listen => 0, Proto => 'tcp', + LocalAddr => 'localhost', LocalPort => 0) ; + if ($sock){ + $this->{port} = $sock->sockport() ; + Inline::Java::debug(2, "next available port number is $this->{port}") ; + close($sock) ; + } + else{ + # Revert to the default. + $this->{port} = - $this->{port} ; + carp( + "Could not get next available port number, using port " . + "$this->{port} instead. Use the PORT configuration " . + "option to suppress this warning.\n Error: $!\n") ; + } + } + else{ + # Revert to the default. + # Try this maybe: 9000 + $$ ? + $this->{port} = - $this->{port} ; + } + } + + # Check if JVM is already running + if ($this->{shared}){ + eval { + $this->reconnect() ; + } ; + if (! $@){ + Inline::Java::debug(1, "connected to already running JVM!") ; + return $this ; + } + + if (! $this->{start_jvm}){ + croak("Can't find running JVM and START_JVM = 0") ; + } + } + + my $java = File::Spec->catfile($o->get_java_config('J2SDK'), + Inline::Java::Portable::portable("J2SDK_BIN"), + ($this->{debugger} ? "jdb" : "java") . + Inline::Java::Portable::portable("EXE_EXTENSION")) ; + + my $shared = ($this->{shared} ? "true" : "false") ; + my $priv = ($this->{private} ? "true" : "false") ; + my $native_doubles = ($o->get_java_config('NATIVE_DOUBLES') ? "true" : "false") ; + my $cmd = Inline::Java::Portable::portable("SUB_FIX_CMD_QUOTES", "\"$java\" $args org.perl.inline.java.InlineJavaServer $debug $this->{bind} $this->{port} $shared $priv $native_doubles") ; + Inline::Java::debug(2, $cmd) ; + if ($o->get_config('UNTAINT')){ + ($cmd) = $cmd =~ /(.*)/ ; + } + + my $pid = 0 ; + eval { + $pid = $this->launch($o, $cmd) ; + } ; + croak "Can't exec JVM: $@" if $@ ; + + if ($this->{shared}){ + # As of 0.40, we release by default. + $this->release() ; + } + else{ + $this->capture() ; + } + + $this->{pid} = $pid ; + $this->{socket} = setup_socket( + $this->{host}, + $this->{port}, + # Give the user an extra hour's time set breakpoints and the like... + ($this->{debugger} ? 3600 : 0) + int($o->get_java_config('STARTUP_DELAY')), + 0 + ) ; + } + + return $this ; +} + + +sub launch { + my $this = shift ; + my $o = shift ; + my $cmd = shift ; + + local $SIG{__WARN__} = sub {} ; + + my $dn = Inline::Java::Portable::portable("DEV_NULL") ; + my $in = ($this->{debugger} ? ">&STDIN" : new IO::File("<$dn")) ; + if (! defined($in)){ + croak "Can't open $dn for reading" ; + } + + my $out = ">&STDOUT" ; + if ($this->{shared}){ + $out = new IO::File(">$dn") ; + if (! defined($out)){ + croak "Can't open $dn for writing" ; + } + } + + my $err = ">&STDERR" ; + + my $pid = open3($in, $out, $err, $cmd) ; + + if (! $this->{debugger}){ + close($in) ; + } + if ($this->{shared}){ + close($out) ; + } + + return $pid ; +} + + +sub DESTROY { + my $this = shift ; + + $this->shutdown() ; +} + + +sub shutdown { + my $this = shift ; + + if ($this->{embedded}){ + Inline::Java::debug(1, "embedded JVM, skipping shutdown.") ; + return ; + } + + if (! $this->{destroyed}){ + if ($this->am_owner()){ + Inline::Java::debug(1, "JVM owner exiting...") ; + + if ($this->{socket}){ + # This asks the Java server to stop and die. + my $sock = $this->{socket} ; + if ($sock->peername()){ + Inline::Java::debug(1, "Sending 'die' message to JVM...") ; + print $sock "die\n" ; + } + else{ + carp "Lost connection with Java virtual machine" ; + } + close($sock) ; + + if ($this->{pid}){ + # Here we go ahead and send the signals anyway to be very + # sure it's dead... + # Always be polite first, and then insist. + if (Inline::Java::Portable::portable('GOT_SAFE_SIGNALS')){ + Inline::Java::debug(1, "Sending 15 signal to JVM...") ; + kill(15, $this->{pid}) ; + Inline::Java::debug(1, "Sending 9 signal to JVM...") ; + kill(9, $this->{pid}) ; + } + + # Reap the child... + waitpid($this->{pid}, 0) ; + } + } + if ($this->{JNI}){ + $this->{JNI}->shutdown() ; + } + } + else{ + # We are not the JVM owner, so we simply politely disconnect + if ($this->{socket}){ + Inline::Java::debug(1, "JVM non-owner exiting...") ; + close($this->{socket}) ; + $this->{socket} = undef ; + } + + # This should never happen in JNI mode + } + + $this->{destroyed} = 1 ; + } +} + + +# This cannot be a member function because it can be used +# elsewhere to connect to the JVM. +sub setup_socket { + my $host = shift ; + my $port = shift ; + my $timeout = shift ; + my $one_shot = shift ; + + my $socket = undef ; + + my $last_words = "timeout\n" ; + my $got_alarm = Inline::Java::Portable::portable("GOT_ALARM") ; + + eval { + local $SIG{ALRM} = sub { die($last_words) ; } ; + + if ($got_alarm){ + alarm($timeout) ; + } + + # ignore expected "connection refused" warnings + # Thanks binkley! + local $SIG{__WARN__} = sub { + warn($@) unless ($@ =~ /Connection refused/i) ; + } ; + + while (1){ + $socket = new IO::Socket::INET( + PeerAddr => $host, + PeerPort => $port, + Proto => 'tcp') ; + if (($socket)||($one_shot)){ + last ; + } + select(undef, undef, undef, 0.1) ; + } + + if ($got_alarm){ + alarm(0) ; + } + } ; + if ($@){ + if ($@ eq $last_words){ + croak "JVM taking more than $timeout seconds to start, or died before Perl could connect. Increase config STARTUP_DELAY if necessary." ; + } + else{ + if ($got_alarm){ + alarm(0) ; + } + croak $@ ; + } + } + + if (! $socket){ + croak "Can't connect to JVM at ($host:$port): $!" ; + } + + $socket->autoflush(1) ; + + return $socket ; +} + + +sub reconnect { + my $this = shift ; + + if (($this->{JNI})||(! $this->{shared})){ + return ; + } + + if ($this->{socket}){ + # Close the previous socket + close($this->{socket}) ; + $this->{socket} = undef ; + } + + my $socket = setup_socket( + $this->{host}, + $this->{port}, + 0, + 1 + ) ; + $this->{socket} = $socket ; + + # Now that we have reconnected, we release the JVM + $this->release() ; +} + + +sub capture { + my $this = shift ; + + if (($this->{JNI})||(! $this->{shared})){ + return ; + } + + foreach my $sig (@SIG_LIST){ + if (exists($SIG{$sig})){ + $SIG{$sig} = \&Inline::Java::done ; + } + } + + $this->{owner} = 1 ; +} + + +sub am_owner { + my $this = shift ; + + return $this->{owner} ; +} + + +sub release { + my $this = shift ; + + if (($this->{JNI})||(! $this->{shared})){ + return ; + } + + foreach my $sig (@SIG_LIST){ + local $SIG{__WARN__} = sub {} ; + if (exists($SIG{$sig})){ + $SIG{$sig} = $SIGS{$sig} ; + } + } + + $this->{owner} = 0 ; +} + + +sub process_command { + my $this = shift ; + my $inline = shift ; + my $data = shift ; + + my $resp = undef ; + + # Patch by Simon Cozens for perl -wle 'use Our::Module; do_stuff()' + local $/ = "\n" ; + local $\ = "" ; + # End Patch + + while (1){ + Inline::Java::debug(3, "packet sent is $data") ; + + if ($this->{socket}){ + + my $sock = $this->{socket} ; + print $sock $data . "\n" or + croak "Can't send packet to JVM: $!" ; + + $resp = <$sock> ; + if (! $resp){ + croak "Can't receive packet from JVM: $!" ; + } + + # Release the reference since the object has been sent back + # to Java. + $Inline::Java::Callback::OBJECT_HOOK = undef ; + } + if ($this->{JNI}){ + $Inline::Java::JNI::INLINE_HOOK = $inline ; + $resp = $this->{JNI}->process_command($data) ; + } + chomp($resp) ; + + Inline::Java::debug(3, "packet recv is $resp") ; + + # We got an answer from the server. Is it a callback? + if ($resp =~ /^callback/o){ + ($data, $Inline::Java::Callback::OBJECT_HOOK) = Inline::Java::Callback::InterceptCallback($inline, $resp) ; + next ; + } + else{ + last ; + } + } + + return $resp ; +} + + + +1 ; + diff --git a/Java/Makefile.PL b/Java/Makefile.PL new file mode 100644 index 0000000..2349f48 --- /dev/null +++ b/Java/Makefile.PL @@ -0,0 +1,357 @@ +use ExtUtils::MakeMaker ; +use File::Find ; + +use strict ; +use File::Spec ; + +require "Portable.pm" ; +# The file we just produced in the parent Makefile.PL +require "default_j2sdk.pl" ; + +my $build_jni = $main::build_jni ; +my $build_perl_natives = $main::build_perl_natives ; +my $build_perl_interpreter = $main::build_perl_interpreter ; +my $jvm_lib_type = $main::jvm_lib_type ; + +my $jvm_lib = Inline::Java::Portable::portable('JVM_LIB') ; +my $jvm_so = Inline::Java::Portable::portable('JVM_SO') ; + +my %so_dirs = () ; + +my @files = ( + 'jni.h', + 'jni_md.h', + $jvm_lib, +) ; +if ($jvm_so ne $jvm_lib){ + push @files, $jvm_so ; +} +push @files, 'jvm.cfg' ; + +my $files = { + 'jni.h' => { + discard => qr/include-old/, + }, + 'jni_md.h' => { + discard => qr/include-old/, + }, + $jvm_lib => { + }, + 'jvm.cfg' => { + }, + $jvm_so => { + }, +} ; + +foreach my $f (@files){ + $files->{$f}->{selected} = undef ; + $files->{$f}->{choices} = [] ; + $files->{$f}->{default_choice} = 1 ; +} + + +my $build_jni_by_dflt = Inline::Java::Portable::portable("BUILD_JNI_BY_DFLT") ; +if (! defined($build_jni)){ + print + "\nInline::Java can use a JNI extension that allows the Java Virtual Machine\n" . + "(JVM) to be dynamically linked with Perl instead of running as a separate\n" . + "process. The use of this extension is optional, and building it still\n" . + "allows Inline::Java to run the JVM in the default (separate process)\n" . + "fashion.\n" . + "Note: You need a C compiler to build the extension.\n" ; +# "Note: You must build the extension if you wish to use PerlNatives or\n" . +# " PerlInterpreter.\n" ; + if (AskYN("Do you wish to build the JNI extension?", + ($build_jni_by_dflt ? 'y' : 'n'))){ + $build_jni = 1 ; + } +} +if ($build_jni){ + print "\nBuilding JNI extension.\n\n" ; + + my $jdk_dir = Inline::Java::get_default_j2sdk() ; + + my $symlink = Inline::Java::Portable::portable("GOT_SYMLINK") ; + find( + { + wanted => \&search, + ($symlink ? (follow_fast => 1, follow_skip => 2) : ()), + }, + $jdk_dir) ; + + my $type = FindDefaultVMType() ; + if (defined($type)){ + my $cnt = 1 ; + foreach my $c (@{$files->{$jvm_so}->{choices}}){ + if ($c =~ /$type/){ + $files->{$jvm_so}->{default_choice} = $cnt ; + } + $cnt++ ; + } + } + + # We no longer need jvm.cfg from now on... + pop @files ; + + my $done = 0 ; + foreach my $f (@files){ + my $cnt = scalar(@{$files->{$f}->{choices}}) ; + if ($cnt == 0){ + print "Can't locate file '$f' anywhere under '$jdk_dir'\n" ; + $done = 1 ; + last ; + } + elsif ($cnt == 1){ + $files->{$f}->{selected} = $files->{$f}->{choices}->[0] ; + } + else { + my $choose = 1 ; + if (($f eq $jvm_lib)&&(defined($jvm_lib_type))){ + my @matches = grep {/$jvm_lib_type/} @{$files->{$f}->{choices}} ; + if (! scalar(@matches)){ + print "WARNING: No $f type matching '$jvm_lib_type' found.\n\n" ; + } + elsif (scalar(@matches) == 1){ + print "Automatically selecting '$matches[0]' for $f type.\n\n" ; + $files->{$f}->{selected} = $matches[0] ; + $choose = 0 ; + } + } + Choose($f) if $choose ; + } + } + if (! $done){ + # We have all the required files selected. + + CleanSoDirs() ; + + # Cygwin: create gcc-compatible library wrapper for jvm.dll + if ($^O eq 'cygwin') { + my $dll = File::Spec->catfile($jdk_dir, 'lib', 'libjvm.dll.a') ; + print "Creating '$dll' for cygwin.\n\n" ; + system("/usr/bin/dlltool --input-def jvm.def --kill-at --dllname jvm.dll --output-lib '$dll'") + and print "Error attempting to create '$jdk_dir/lib/libjvm.dll.a'\n" ; + } + + print "Building with:\n" ; + map { print " " . File::Spec->catfile($files->{$_}->{selected}, $_) . "\n" ;} @files ; + + $done = 0 ; + if (! $done){ + print + "\nNote: In order for Inline::Java to use the JNI extension, you will need to\n" . + "use the JNI configuration option or set the PERL_INLINE_JAVA_JNI environment\n" . + "variable to a true value. You will also need to add the following directories\n" . + "to your " . Inline::Java::Portable::portable('SO_LIB_PATH_VAR') . " environment variable:\n" ; + @main::SO_DIRS = keys %so_dirs ; + map {print " $_\n"; } @main::SO_DIRS ; + print "See README.JNI for more information.\n\n" ; + + @main::I = map { Inline::Java::Portable::portable('SUB_FIX_MAKE_QUOTES', "-I$_") } + ($files->{'jni.h'}->{selected}, $files->{'jni_md.h'}->{selected}) ; + @main::L = map { Inline::Java::Portable::portable('SUB_FIX_MAKE_QUOTES', "-L$_") } + ($files->{$jvm_lib}->{selected}) ; + + my $DIR = [] ; + if (! defined($build_perl_interpreter)){ + print <<TXT; +The PerlInterpreter extension allows Inline::Java to be loaded directly from +Java using an embedded Perl interpreter. It is still EXPERIMENTAL and +may not build or work properly on all platforms. See documentation for +more details. +TXT + if (AskYN("Do you wish to build the PerlInterpreter extension?", 'n')){ + $build_perl_interpreter = 1 ; + } + print "\n" ; + } + if ($build_perl_interpreter){ + push @{$DIR}, 'PerlInterpreter' ; + } + + if (! defined($build_perl_natives)){ + print <<TXT; +The PerlNatives extension allows for callbacks to be defined as native +Java methods. It is still EXPERIMENTAL and may not build or work properly +on all platforms. See documentation for more details. +Note: PerlNatives requires J2SDK 1.4 or greater. +TXT + if (AskYN("Do you wish to build the PerlNatives extension?", 'n')){ + $build_perl_natives = 1 ; + } + print "\n" ; + } + if ($build_perl_natives){ + push @{$DIR}, 'PerlNatives' ; + } + + + WriteMakefile( + NAME => 'Inline::Java::JNI', + VERSION_FROM => 'JNI.pm', + DIR => $DIR, + PMLIBDIRS => [File::Spec->catdir('sources', 'org', 'perl', 'inline', 'java')], + INC => join(' ', @main::I), + LIBS => [join(' ', @main::L) . " -ljvm"], + dynamic_lib => { + OTHERLDFLAGS => Inline::Java::Portable::portable('OTHERLDFLAGS') + }, + # CCFLAGS => '-D_REENTRANT', + ) ; + } + } +} +else{ + print "\n" ; + WriteMakefile( + NAME => 'Inline::Java::JNI', + VERSION_FROM => 'JNI.pm', + DIR => [], + PMLIBDIRS => [File::Spec->catdir('sources', 'org', 'perl', 'inline', 'java')], + XS => {}, + C => [] + ) ; +} + + +################################################# + + +sub search { + my $file = $_ ; + + my $ext = Inline::Java::Portable::portable('SO_EXT') ; + if ($File::Find::dir =~ /jre/){ + if ($file =~ /\.$ext$/){ + my $dir = File::Spec->canonpath($File::Find::dir) ; + $so_dirs{$dir} = 1 ; + } + } + + foreach my $f (@files){ + if ($file eq $f){ + my $re = $files->{$f}->{discard} ; + if ((! $re)||($File::Find::dir !~ /$re/)){ + push @{$files->{$f}->{choices}}, File::Spec->canonpath($File::Find::dir) ; + } + last ; + } + } +} + + +sub CleanSoDirs { + foreach my $d (keys %so_dirs){ + if (-e File::Spec->catfile($d, $jvm_so)){ + delete $so_dirs{$d} ; + } + elsif ($d =~ /plugin/){ + delete $so_dirs{$d} ; + } + elsif ($d =~ /motif/){ + delete $so_dirs{$d} ; + } + elsif ($d =~ /javaws/){ + delete $so_dirs{$d} ; + } + elsif ($d =~ /headless/){ + delete $so_dirs{$d} ; + } + elsif ($d =~ /xawt/){ + delete $so_dirs{$d} ; + } + elsif ($d =~ /_threads/){ + if ($d !~ /native_threads/){ + delete $so_dirs{$d} ; + } + } + } + $so_dirs{$files->{$jvm_so}->{selected}} = 1 ; +} + + +sub FindDefaultVMType { + my $type = undef ; + my $choices = $files->{'jvm.cfg'}->{choices} ; + if (scalar(@{$choices})){ + my $cfg = File::Spec->catfile($choices->[0], 'jvm.cfg') ; + if (open(CFG, "<$cfg")){ + while (<CFG>){ + my $line = $_ ; + chomp($line) ; + $line =~ s/^\s+// ; + $line =~ s/\s+$// ; + + if (! $line){ + next ; + } + elsif ($line =~ /^#/){ + next ; + } + else{ + ($type) = split(/\s+/, $line) ; + $type =~ s/^-// ; + last ; + } + } + close(CFG) ; + } + } + + return $type ; +} + + +sub Choose { + my $f = shift ; + + my $o = $files->{$f} ; + my $cnt = 0 ; + my $def = undef ; + foreach my $f (@{$o->{choices}}){ + $cnt++ ; + print "$cnt) $f\n" ; + } + my $idx = AskSub("Please select from the above list which '$f' to use:", + $o->{default_choice}, sub {(($_[0] >= 1)&&($_[0] <= $cnt))}) ; + + $o->{selected} = $o->{choices}->[int($idx) - 1] ; + print "\n" ; +} + + +# Gets string from stdin +sub Ask { + my $ques = shift ; + my $def = shift ; + + return AskSub($ques, $def, undef) ; +} + + +# Gets yes/no from stdin +sub AskYN { + my $ques = shift ; + my $def = shift ; + + my $ans = AskSub($ques, $def, sub {((! $_[0])||($_[0] =~ /^(y|n)$/i))}) ; + + return (($ans =~ /^y$/i) ? 1 : 0) ; +} + + +sub AskSub { + my $ques = shift ; + my $def = shift ; + my $sub = shift ; + + while (1){ + my $ans = prompt($ques, $def) ; + if (! $sub){ + return $ans ; + } + elsif ($sub->($ans)){ + return $ans ; + } + } +} diff --git a/Java/Object.pm b/Java/Object.pm new file mode 100644 index 0000000..2f56856 --- /dev/null +++ b/Java/Object.pm @@ -0,0 +1,547 @@ +package Inline::Java::Object ; +@Inline::Java::Object::ISA = qw(Inline::Java::Object::Tie) ; + +use strict ; +use Inline::Java::Protocol ; +use Carp ; + +$Inline::Java::Object::VERSION = '0.53' ; + +# Here we store as keys the knots and as values our blessed private objects +my $PRIVATES = {} ; + + +# Bogus constructor. We fall here if no public constructor is defined +# in the Java class. +sub new { + my $class = shift ; + + croak "No public constructor defined for class $class" ; +} + + +# Constructor. Here we create a new object that will be linked +# to a real Java object. +sub __new { + my $class = shift ; + my $java_class = shift ; + my $inline = shift ; + my $objid = shift ; + my $proto = shift ; + my $args = shift ; + + my %this = () ; + + my $knot = tie %this, $class ; + my $this = bless(\%this, $class) ; + + my $pkg = $inline->get_api('pkg') ; + if ($class ne "Inline::Java::Object"){ + $class = Inline::Java::java2perl($pkg, $java_class) ; + } + + my $priv = Inline::Java::Object::Private->new($class, $java_class, $inline) ; + $PRIVATES->{$knot} = $priv ; + + if ($objid <= -1){ + my $obj = undef ; + eval { + $obj = $this->__get_private()->{proto}->CreateJavaObject($java_class, $proto, $args) ; + } ; + croak $@ if $@ ; + + if (! defined($this->__get_private()->{id})){ + # Use created a java::lang::String or something... + return $obj ; + } + } + else{ + $this->__get_private()->{id} = $objid ; + Inline::Java::debug(2, "creating object in java ($class):") ; + } + + Inline::Java::debug_obj($this) ; + + return $this ; +} + + +sub __get_private { + my $this = shift ; + + my $knot = tied(%{$this}) || $this ; + + my $priv = $PRIVATES->{$knot} ; + if (! defined($priv)){ + croak "Unknown Java object reference $knot" ; + } + + return $priv ; +} + + +# Checks to make sure all the arguments can be "cast" to prototype +# types. +sub __validate_prototype { + my $this = shift ; + my $method = shift ; + my $args = shift ; + my $protos = shift ; + my $inline = shift ; + + my @matched = () ; + + my @proto_values = values %{$protos} ; + my @errors = () ; + foreach my $s (@proto_values){ + my $proto = $s->{SIGNATURE} ; + my $stat = $s->{STATIC} ; + my $idx = $s->{IDX} ; + my $new_args = undef ; + my $score = undef ; + + my $sig = Inline::Java::Protocol->CreateSignature($proto) ; + Inline::Java::debug(3, "matching arguments to $method$sig") ; + + eval { + ($new_args, $score) = Inline::Java::Class::CastArguments($args, $proto, $inline) ; + } ; + if ($@){ + if (scalar(@proto_values) == 1){ + # Here we have only 1 prototype, so we return the error. + croak $@ ; + } + push @errors, $@ ; + Inline::Java::debug(3, "error trying to fit args to prototype: $@") ; + next ; + } + + # We passed! + Inline::Java::debug(3, "match successful: score is $score") ; + my $h = { + PROTO => $proto, + NEW_ARGS => $new_args, + NB_ARGS => scalar(@{$new_args}), + SCORE => $score, + STATIC => $stat, + IDX => $idx, + } ; + + # Tiny optimization: abort if type coerce was used and matched for + # every parameter + if (Inline::Java::Class::IsMaxArgumentsScore($new_args, $score)){ + Inline::Java::debug(3, "perfect match found, aborting search") ; + @matched = () ; + push @matched, $h ; + last ; + } + else{ + push @matched, $h ; + } + } + + my $nb_matched = scalar(@matched) ; + if (! $nb_matched){ + my $name = (ref($this) ? $this->__get_private()->{class} : $this) ; + my $sa = Inline::Java::Protocol->CreateSignature($args) ; + my $msg = "In method $method of class $name: Can't find any signature that matches " . + "the arguments passed $sa.\nAvailable signatures are:\n" ; + my $i = 0 ; + foreach my $s (@proto_values){ + my $proto = $s->{SIGNATURE} ; + my $static = ($s->{STATIC} ? "static " : "") ; + + my $sig = Inline::Java::Protocol->CreateSignature($proto) ; + $msg .= "\t$static$method$sig\n" ; + $msg .= "\t\terror was: $errors[$i]" ; + $i++ ; + } + chomp $msg ; + croak $msg ; + } + + my $chosen = undef ; + foreach my $h (@matched){ + my $idx = ($chosen ? $chosen->{IDX} : 0) ; + my $max = ($chosen ? $chosen->{SCORE} : 0) ; + + my $s = $h->{SCORE} ; + my $i = $h->{IDX} ; + if ($s > $max){ + $chosen = $h ; + } + elsif ($s == $max){ + # Here if the scores are equal we take the last one since + # we start with inherited methods and move to class mothods + if ($i > $idx){ + $chosen = $h ; + } + } + } + + if ((! $chosen->{STATIC})&&(! ref($this))){ + # We are trying to call an instance method without an object + # reference + croak "Method $method of class $this must be called from an object reference" ; + } + + # Here we will be polite and warn the user if we had to choose a + # method by ourselves. + if ($inline->get_java_config('WARN_METHOD_SELECT')){ + if (($nb_matched > 1)&& + ($chosen->{SCORE} < ($chosen->{NB_ARGS} * 10))){ + my $msg = "Based on the arguments passed, I had to choose between " . + "the following method signatures:\n" ; + foreach my $m (@matched){ + my $s = Inline::Java::Protocol->CreateSignature($m->{PROTO}) ; + my $c = ($m eq $chosen ? "*" : " ") ; + $msg .= " $c $method$s\n" ; + } + $msg .= "I chose the one indicated by a star (*). To force " . + "the use of another signature or to disable this warning, use " . + "the casting functionality described in the documentation." ; + carp $msg ; + } + } + + return ( + $chosen->{PROTO}, + $chosen->{NEW_ARGS}, + $chosen->{STATIC}, + ) ; +} + + +sub __isa { + my $this = shift ; + my $proto = shift ; + + my $ret = undef ; + eval { + $ret = $this->__get_private()->{proto}->ISA($proto) ; + } ; + if ($@){ + return ($@, 0) ; + } + + if ($ret == -1){ + my $c = $this->__get_private()->{java_class} ; + return ("$c is not a kind of $proto", 0) ; + } + + return ('', $ret) ; +} + + +sub __cast { + my $this = shift ; + my $class = shift ; + + my $ret = $this->__get_private()->{proto}->Cast($class) ; + + return $ret ; +} + + +sub __get_member { + my $this = shift ; + my $key = shift ; + + if ($this->__get_private()->{class} eq "Inline::Java::Object"){ + croak "Can't get member '$key' for an object that is not bound to Perl" ; + } + + Inline::Java::debug(3, "fetching member variable '$key'") ; + + my $inline = $this->__get_private()->{inline} ; + my $fields = $inline->get_fields($this->__get_private()->{class}) ; + + my $types = $fields->{$key} ; + if ($types){ + # We take the last one, which is more specific. Eventually + # we should use a scoring method just like for the methods + my $sign = undef ; + foreach my $s (values %{$types}){ + if ((! defined($sign))||($s->{IDX} > $sign->{IDX})){ + $sign = $s ; + } + } + + my $proto = $sign->{TYPE} ; + + my $ret = $this->__get_private()->{proto}->GetJavaMember($key, [$proto], [undef]) ; + Inline::Java::debug(3, "returning member (" . ($ret || '') . ")") ; + + return $ret ; + } + else{ + my $name = $this->__get_private()->{class} ; + croak "No public member variable '$key' defined for class '$name'" ; + } +} + + +sub __set_member { + my $this = shift ; + my $key = shift ; + my $value = shift ; + + if ($this->__get_private()->{class} eq "Inline::Java::Object"){ + croak "Can't set member '$key' for an object that is not bound to Perl" ; + } + + my $inline = $this->__get_private()->{inline} ; + my $fields = $inline->get_fields($this->__get_private()->{class}) ; + + my $types = $fields->{$key} ; + if ($types){ + # We take the last one, which is more specific. Eventually + # we should use a scoring method just like for the methods + my $sign = undef ; + foreach my $s (values %{$types}){ + if ((! defined($sign))||($s->{IDX} > $sign->{IDX})){ + $sign = $s ; + } + } + + my $proto = $sign->{TYPE} ; + my $new_args = undef ; + my $score = undef ; + + ($new_args, $score) = Inline::Java::Class::CastArguments([$value], [$proto], $this->__get_private()->{inline}) ; + $this->__get_private()->{proto}->SetJavaMember($key, [$proto], $new_args) ; + } + else{ + my $name = $this->__get_private()->{class} ; + croak "No public member variable '$key' defined for class '$name'" ; + } +} + + +sub AUTOLOAD { + my $this = shift ; + my @args = @_ ; + + use vars qw($AUTOLOAD) ; + my $func_name = $AUTOLOAD ; + # Strip package from $func_name, Java will take of finding the correct + # method. + $func_name =~ s/^(.*)::// ; + + Inline::Java::debug(5, "$func_name") ; + + my $name = (ref($this) ? $this->__get_private()->{class} : $this) ; + if ($name eq "Inline::Java::Object"){ + croak "Can't call method '$func_name' on an object ($name) that is not bound to Perl" ; + } + + croak "No public method '$func_name' defined for class '$name'" ; +} + + +sub DESTROY { + my $this = shift ; + + my $knot = tied %{$this} ; + if (! $knot){ + Inline::Java::debug(4, "destroying Inline::Java::Object::Tie") ; + + if (! Inline::Java::get_DONE()){ + + my $class = $this->__get_private()->{class} ; + Inline::Java::debug(2, "destroying object in java ($class):") ; + + { + local $@ ; + eval { + $this->__get_private()->{proto}->DeleteJavaObject($this) ; + } ; + if ($@){ + # We croaked here. Was there already a pending $@? + my $name = $this->__get_private()->{class} ; + croak "In method DESTROY of class $name: $@" ; + } + } + + # Here we have a circular reference so we need to break it + # so that the memory is collected. + my $priv = $this->__get_private() ; + my $proto = $priv->{proto} ; + $priv->{proto} = undef ; + $proto->{obj_priv} = undef ; + $PRIVATES->{$this} = undef ; + } + else{ + Inline::Java::debug(4, "script marked as DONE, object destruction not propagated to Java") ; + } + } + else{ + Inline::Java::debug(4, "destroying Inline::Java::Object") ; + } +} + + + +######################## Hash Methods ######################## +package Inline::Java::Object::Tie ; +@Inline::Java::Object::Tie::ISA = qw(Tie::StdHash) ; + + +use Tie::Hash ; +use Carp ; + + +sub TIEHASH { + my $class = shift ; + + return $class->SUPER::TIEHASH(@_) ; +} + + +sub STORE { + my $this = shift ; + my $key = shift ; + my $value = shift ; + + return $this->__set_member($key, $value) ; +} + + +sub FETCH { + my $this = shift ; + my $key = shift ; + + return $this->__get_member($key) ; +} + + +sub FIRSTKEY { + my $this = shift ; + + return $this->SUPER::FIRSTKEY() ; +} + + +sub NEXTKEY { + my $this = shift ; + + return $this->SUPER::NEXTKEY() ; +} + + +sub EXISTS { + my $this = shift ; + my $key = shift ; + + my $inline = $this->__get_private()->{inline} ; + my $fields = $inline->get_fields($this->__get_private()->{class}) ; + + if ($fields->{$key}){ + return 1 ; + } + + return 0 ; +} + + +sub DELETE { + my $this = shift ; + my $key = shift ; + + croak "Operation DELETE not supported on Java object" ; +} + + +sub CLEAR { + my $this = shift ; + + croak "Operation CLEAR not supported on Java object" ; +} + + +sub DESTROY { + my $this = shift ; +} + + + + +######################## Static Member Methods ######################## +package Inline::Java::Object::StaticMember ; +@Inline::Java::Object::StaticMember::ISA = qw(Tie::StdScalar) ; + + +use Tie::Scalar ; +use Carp ; + +my $DUMMIES = {} ; + + +sub TIESCALAR { + my $class = shift ; + my $dummy = shift ; + my $name = shift ; + + my $this = $class->SUPER::TIESCALAR(@_) ; + + $DUMMIES->{$this} = [$dummy, $name] ; + + return $this ; +} + + +sub STORE { + my $this = shift ; + my $value = shift ; + + my ($obj, $key) = @{$DUMMIES->{$this}} ; + + return $obj->__set_member($key, $value) ; +} + + +sub FETCH { + my $this = shift ; + + my ($obj, $key) = @{$DUMMIES->{$this}} ; + + return $obj->__get_member($key) ; +} + + +sub DESTROY { + my $this = shift ; +} + + + +######################## Private Object ######################## +package Inline::Java::Object::Private ; + +sub new { + my $class = shift ; + my $obj_class = shift ; + my $java_class = shift ; + my $inline = shift ; + + my $this = {} ; + $this->{class} = $obj_class ; + $this->{java_class} = $java_class ; + $this->{inline} = $inline ; + $this->{proto} = new Inline::Java::Protocol($this, $inline) ; + + bless($this, $class) ; + + return $this ; +} + + +sub DESTROY { + my $this = shift ; + + Inline::Java::debug(4, "destroying Inline::Java::Object::Private") ; +} + + + +1 ; diff --git a/Java/PerlInterpreter/Makefile.PL b/Java/PerlInterpreter/Makefile.PL new file mode 100644 index 0000000..8d867cd --- /dev/null +++ b/Java/PerlInterpreter/Makefile.PL @@ -0,0 +1,27 @@ +use ExtUtils::MakeMaker ; +use ExtUtils::Embed ; +use Config ; + +use strict ; +require "../Portable.pm" ; + + +my $ccopts = ccopts() ; +chomp($ccopts) ; +my $ldopts = ldopts() ; +chomp($ldopts) ; + +my $pre = Inline::Java::Portable::portable("PRE_WHOLE_ARCHIVE") ; +my $post = Inline::Java::Portable::portable("POST_WHOLE_ARCHIVE") ; +my $dupenv = Inline::Java::Portable::portable("PERL_PARSE_DUP_ENV") ; + +WriteMakefile( + NAME => 'Inline::Java::PerlInterpreter', + VERSION_FROM => 'PerlInterpreter.pm', + CCFLAGS => "$ccopts $dupenv", + LDDLFLAGS => "$pre $ldopts $post $Config{lddlflags}", + INC => join(' ', @main::I), + dynamic_lib => Inline::Java::Portable::portable("dynamic_lib"), + # CCFLAGS => '-D_REENTRANT', +) ; + diff --git a/Java/PerlInterpreter/PerlInterpreter.pm b/Java/PerlInterpreter/PerlInterpreter.pm new file mode 100644 index 0000000..842a09c --- /dev/null +++ b/Java/PerlInterpreter/PerlInterpreter.pm @@ -0,0 +1,17 @@ +package Inline::Java::PerlInterpreter ; + +use strict ; +use Inline::Java ; + +$Inline::Java::PerlInterpreter::VERSION = '0.52' ; + + +use Inline ( + Java => 'STUDY', + EMBEDDED_JNI => 1, + STUDY => [], + NAME => 'Inline::Java::PerlInterpreter', +) ; + + +1 ; diff --git a/Java/PerlInterpreter/PerlInterpreter.pod b/Java/PerlInterpreter/PerlInterpreter.pod new file mode 100644 index 0000000..436117c --- /dev/null +++ b/Java/PerlInterpreter/PerlInterpreter.pod @@ -0,0 +1,115 @@ +=head1 NAME + +Inline::Java::PerlInterpreter - Call Perl directly from Java using Inline::Java. + +=head1 SYNOPSIS + +=for comment + + import org.perl.inline.java.* ; + + class HelpMePerl { + static private InlineJavaPerlInterpreter pi = null ; + + public HelpMePerl() throws InlineJavaException { + } + + static private boolean matches(String target, String pattern) + throws InlineJavaPerlException, InlineJavaException { + Boolean b = (Boolean)pi.eval("'" + target + "' =~ /" + pattern + "/", Boolean.class) ; + return b.booleanValue() ; + } + + public static void main(String args[]) + throws InlineJavaPerlException, InlineJavaException { + pi = InlineJavaPerlInterpreter.create() ; + + String target = "aaabbbccc" ; + String pattern = "ab+" ; + boolean ret = matches(target, pattern) ; + + System.out.println( + target + (ret ? " matches " : " doesn't match ") + pattern) ; + + pi.destroy() ; + } + } + +=for comment + + +=head1 DESCRIPTION + +WARNING: C<Inline::Java::PerlInterpreter> is still experimental. + +The C<org.perl.inline.java.InlineJavaPerlInterpreter> Java class allows +you to load a Perl interpreter directly from Java. You can then perform +regular callbacks to call into Perl. + Z<> + + +=head1 USING THE org.perl.inline.java.InlineJavaPerlInterpreter CLASS + +B<Installation> + +Before using C<org.perl.inline.java.InlineJavaPerlInterpreter>, you must +have installed C<Inline::Java> as well as the JNI extension. Additionally, +the PerlInterpreter extension must also have been installed. + +B<Finding the jar> + +To be able to use the C<org.perl.inline.java.InlineJavaPerlInterpreter> +class, you must use the jar file provided by C<Inline::Java>. You can +easily locate this jar file using the following command: + + % perl -MInline::Java=jar + +You must then add this jar file to your CLASSPATH as you would any jar +file. + +B<Basic Usage> + +C<org.perl.inline.java.InlineJavaPerlInterpreter> itself extends +C<org.perl.inline.java.InlineJavaPerlCaller>. See L<Inline::Java::Callback> +for information on the callback API. + +Besides that API, C<org.perl.inline.java.InlineJavaPerlInterpreter> provides +only 2 other public methods: + +=over 4 + +=item public InlineJavaPerlInterpreter create() +throws InlineJavaPerlException, InlineJavaException + +Creates a new org.perl.inline.java.InlineJavaPerlInterpreter object. +This class in a singleton. + +=item public void destroy() + +Destroys the Perl interpreter. + +=back + + +=head1 SEE ALSO + +L<Inline::Java>, L<Inline::Java::Callback>, L<Inline::Java::PerlNatives>. + Z<> + + +=head1 AUTHOR + +Patrick LeBoutillier <patl@cpan.org> is the author of Inline::Java. + Z<> + + +=head1 COPYRIGHT + +Copyright (c) 2001-2004, Patrick LeBoutillier. + +All Rights Reserved. This module is free software. It may be used, +redistributed and/or modified under the terms of the Perl Artistic +License. See http://www.perl.com/perl/misc/Artistic.html for more +details. + +=cut diff --git a/Java/PerlInterpreter/PerlInterpreter.xs b/Java/PerlInterpreter/PerlInterpreter.xs new file mode 100644 index 0000000..d8d5ac9 --- /dev/null +++ b/Java/PerlInterpreter/PerlInterpreter.xs @@ -0,0 +1,95 @@ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#ifdef __CYGWIN__ + #include "w32api/basetyps.h" +#endif + +/* Include the JNI header file */ +#include "jni.h" + +/* The PerlInterpreter handle */ +PerlInterpreter *interp = NULL ; + + +/* XS initialisation stuff */ +void boot_DynaLoader(pTHX_ CV* cv) ; + + +static void xs_init(pTHX){ + char *file = __FILE__ ; + dXSUB_SYS ; + newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file) ; +} + + + +void throw_ijp(JNIEnv *env, char *msg){ + jclass ije ; + + ije = (*(env))->FindClass(env, "org/perl/inline/java/InlineJavaPerlException") ; + if ((*(env))->ExceptionCheck(env)){ + (*(env))->ExceptionDescribe(env) ; + (*(env))->ExceptionClear(env) ; + (*(env))->FatalError(env, "Can't find class InlineJavaPerlException: exiting...") ; + } + (*(env))->ThrowNew(env, ije, msg) ; +} + + +JNIEXPORT void JNICALL Java_org_perl_inline_java_InlineJavaPerlInterpreter_construct(JNIEnv *env, jclass cls){ + char *args[] = {"inline-java", "-e1"} ; + char **envdup = NULL ; + +#ifdef PERL_PARSE_ENV_DUP + int envl = 0 ; + int i = 0 ; + /* This will leak, but it's a one shot... */ + for (i = 0 ; environ[i] != NULL ; i++){ + envl++ ; + } + envdup = (char **)calloc(envl + 1, sizeof(char *)) ; + for (i = 0 ; i < envl ; i++){ + envdup[i] = strdup(environ[i]) ; + } +#endif + + interp = perl_alloc() ; + perl_construct(interp) ; + perl_parse(interp, xs_init, 2, args, envdup) ; + perl_run(interp) ; +} + + +JNIEXPORT void JNICALL Java_org_perl_inline_java_InlineJavaPerlInterpreter_destruct(JNIEnv *env, jclass cls){ + if (interp != NULL){ + perl_destruct(interp) ; + perl_free(interp) ; + interp = NULL ; + } +} + + +JNIEXPORT void JNICALL Java_org_perl_inline_java_InlineJavaPerlInterpreter_evalNoReturn(JNIEnv *env, jclass cls, jstring code){ + SV *sv = NULL ; + char *pcode = NULL ; + + pcode = (char *)((*(env))->GetStringUTFChars(env, code, NULL)) ; + sv = sv_2mortal(newSVpv(pcode, 0)) ; + /* sv = eval_pv(pcode, FALSE) ; */ + eval_sv(sv, G_EVAL|G_KEEPERR) ; + (*(env))->ReleaseStringUTFChars(env, code, pcode) ; + if (SvTRUE(ERRSV)){ + STRLEN n_a ; + throw_ijp(env, SvPV(ERRSV, n_a)) ; + } +} + + + +MODULE = Inline::Java::PerlInterpreter PACKAGE = Inline::Java::PerlInterpreter + +PROTOTYPES: DISABLE + + diff --git a/Java/PerlInterpreter/t/01_init.t b/Java/PerlInterpreter/t/01_init.t new file mode 100644 index 0000000..d889213 --- /dev/null +++ b/Java/PerlInterpreter/t/01_init.t @@ -0,0 +1,10 @@ +use strict ; +use Test ; + +BEGIN { + plan(tests => 1) ; + mkdir('./_Inline_test', 0777) unless -e './_Inline_test' ; +} + + +ok(1) ; diff --git a/Java/PerlInterpreter/t/02_perl_interpreter.t b/Java/PerlInterpreter/t/02_perl_interpreter.t new file mode 100644 index 0000000..dc04efa --- /dev/null +++ b/Java/PerlInterpreter/t/02_perl_interpreter.t @@ -0,0 +1,146 @@ +use strict ; + +use Test ; +use File::Spec ; +use Config ; + +BEGIN { + if ($^O eq 'cygwin'){ + # Stand-alone Java interpreter cannot load Cygwin DLL directly + plan(tests => 0) ; + exit ; + } + + plan(tests => 12) ; +} + + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 'DATA', + NAME => 'Tests' +) ; +use Inline::Java::Portable ; +ok(1) ; + + +my $inline = $org::perl::inline::java::InlineJavaPerlInterpreterTests::INLINE ; +$inline = $org::perl::inline::java::InlineJavaPerlInterpreterTests::INLINE ; # stupid warning... + +my $install_dir = File::Spec->catdir($inline->get_api('install_lib'), + 'auto', $inline->get_api('modpname')) ; + +require Inline::Java->find_default_j2sdk() ; +my $server_jar = Inline::Java::Portable::get_server_jar() ; + +run_java($install_dir, $server_jar) ; + + +################################################# + + +sub run_java { + my @cps = @_ ; + + $ENV{CLASSPATH} = Inline::Java::Portable::make_classpath(@cps) ; + Inline::Java::debug(1, "CLASSPATH is $ENV{CLASSPATH}\n") ; + + my $java = File::Spec->catfile( + Inline::Java::get_default_j2sdk(), + Inline::Java::Portable::portable("J2SDK_BIN"), + 'java' . Inline::Java::Portable::portable("EXE_EXTENSION")) ; + + my $debug = $ENV{PERL_INLINE_JAVA_DEBUG} || 0 ; + my $cmd = Inline::Java::Portable::portable("SUB_FIX_CMD_QUOTES", "\"$java\" " . + "org.perl.inline.java.InlineJavaPerlInterpreterTests $debug") ; + Inline::Java::debug(1, "Command is $cmd\n") ; + open(CMD, "$cmd|") or die("Can't execute $cmd: $!") ; + while (<CMD>){ + print $_ ; + } +} + + +__END__ + +__Java__ +package org.perl.inline.java ; + +class InlineJavaPerlInterpreterTests implements Runnable { + private static int cnt = 2 ; + private static InlineJavaPerlInterpreter pi = null ; + private static int nb_callbacks_to_run = 5 ; + private static int nb_callbacks_run = 0 ; + + private InlineJavaPerlInterpreterTests() throws InlineJavaException, InlineJavaPerlException { + } + + private synchronized static void ok(Object o1, Object o2){ + if (o1.equals(o2)){ + String comment = " # " + o1 + " == " + o2 ; + System.out.println("ok " + cnt + comment) ; + } + else { + String comment = " # " + o1 + " != " + o2 ; + System.out.println("nok " + cnt + comment) ; + } + cnt++ ; + } + + + public void run(){ + try { + String name = (String)pi.CallPerlSub("whats_your_name", null, String.class) ; + ok(name, "perl") ; + nb_callbacks_run++ ; + + if (nb_callbacks_run == nb_callbacks_to_run){ + pi.StopCallbackLoop() ; + } + } + catch (Exception e){ + e.printStackTrace() ; + System.exit(1) ; + } + } + + + public static void main(String args[]){ + try { + int debug = 0 ; + if (args.length > 0){ + debug = Integer.parseInt(args[0]) ; + InlineJavaUtils.set_debug(debug) ; + } + + InlineJavaPerlInterpreter.init("test") ; + pi = InlineJavaPerlInterpreter.create() ; + + pi.require("t/Tests.pl") ; + ok("1", "1") ; + pi.require("Carp") ; + ok("1", "1") ; + Integer sum = (Integer)pi.eval("34 + 56", Integer.class) ; + ok(sum, new Integer(90)) ; + String name = (String)pi.CallPerlSub("whats_your_name", null, String.class) ; + ok(name, "perl") ; + + for (int i = 1 ; i <= nb_callbacks_to_run ; i++){ + Thread t = new Thread(new InlineJavaPerlInterpreterTests()) ; + t.start() ; + } + + pi.StartCallbackLoop(); + + pi.destroy() ; + ok("1", "1") ; + } + catch (Exception e){ + e.printStackTrace() ; + System.exit(1) ; + } + ok("1", "1") ; + } +} diff --git a/Java/PerlInterpreter/t/Tests.pl b/Java/PerlInterpreter/t/Tests.pl new file mode 100644 index 0000000..6254ab7 --- /dev/null +++ b/Java/PerlInterpreter/t/Tests.pl @@ -0,0 +1,8 @@ +package main ; +use strict ; + +sub whats_your_name { + return "perl" ; +} + +1 ; diff --git a/Java/PerlNatives/Makefile.PL b/Java/PerlNatives/Makefile.PL new file mode 100644 index 0000000..b5f94dc --- /dev/null +++ b/Java/PerlNatives/Makefile.PL @@ -0,0 +1,16 @@ +use ExtUtils::MakeMaker ; + +use strict ; +require "../Portable.pm" ; + + +WriteMakefile( + NAME => 'Inline::Java::PerlNatives', + VERSION_FROM => 'PerlNatives.pm', + INC => join(' ', @main::I), + LIBS => [join(' ', @main::L) . " -ljvm"], + dynamic_lib => Inline::Java::Portable::portable("dynamic_lib"), + # CCFLAGS => '-D_REENTRANT', + clean => {FILES => "_Inline_test"}, +) ; + diff --git a/Java/PerlNatives/PerlNatives.pm b/Java/PerlNatives/PerlNatives.pm new file mode 100644 index 0000000..dd587ff --- /dev/null +++ b/Java/PerlNatives/PerlNatives.pm @@ -0,0 +1,7 @@ +package Inline::Java::PerlNatives ; + +use strict ; + +$Inline::Java::PerlNatives::VERSION = '0.52' ; + +1 ; diff --git a/Java/PerlNatives/PerlNatives.pod b/Java/PerlNatives/PerlNatives.pod new file mode 100644 index 0000000..f479d28 --- /dev/null +++ b/Java/PerlNatives/PerlNatives.pod @@ -0,0 +1,142 @@ +=head1 NAME + +Inline::Java::PerlNatives - Map Java native methods to Perl functions. + +=head1 SYNOPSIS + +=for comment + + + use Inline Java => <<'END' ; + import org.perl.inline.java.* ; + + class Pod_PN extends InlineJavaPerlNatives { + public Pod_PN() throws InlineJavaException { + } + + native public String hello() ; + } + END + + package Pod_PN ; + sub hello { + return "hi!" ; + } + + package main ; + my $b = new Pod_PN() ; + print($b->hello() . "\n") ; # prints hi! + +=for comment + + +=head1 DESCRIPTION + +WARNING: C<Inline::Java::PerlNatives> is still experimental. + +C<Inline::Java::PerlNatives> allows you to define your callbacks as native +Java methods that are automatically linked to Perl subroutines. You implement +the Perl subroutine directly in the package in which C<Inline::Java> binds +your class. You can do this by making your Java code extend the +C<org.perl.inline.java.InlineJavaPerlNatives> class. + +Note: PerlNatives requires J2SDK version >= 1.4 + Z<> + + +=head1 USING THE org.perl.inline.java.InlineJavaPerlNatives CLASS + +Let's revisit an example from the L<Inline::Java::Callback> documentation: + +=for comment + + use Inline Java => <<'END' ; + import java.util.* ; + import org.perl.inline.java.* ; + import javax.swing.* ; + import java.awt.event.* ; + + class Pod_Button_PN extends InlineJavaPerlNatives + implements ActionListener { + public Pod_Button_PN() throws InlineJavaException { + JFrame frame = new JFrame("Pod_Button") ; + frame.setSize(100,100) ; + JButton button = new JButton("Click Me!") ; + frame.getContentPane().add(button) ; + button.addActionListener(this) ; + frame.show() ; + } + + public void actionPerformed(ActionEvent e){ + button_pressed() ; + } + + native public void button_pressed() ; + } + END + + package Pod_Button_PN ; + sub button_pressed { + print('click!' . "\n") ; # prints click! + $main::b->StopCallbackLoop() ; + } + + package main ; + $main::b = new Pod_Button_PN() ; + $main::b->StartCallbackLoop() ; + +=for comment + +Extending InlineJavaPerlNatives tells C<Inline::Java> that all native methods +declared in that class should be linked to Perl subroutines implemented in the +approriate package. You can then call these methods from Java just like regular +methods. You can even call them from Perl if they are public. + Z<> + + +=head1 BUGS AND DEFICIENCIES + +C<Inline::Java::PerlNatives> has a few limits that one must be aware of: + +=over 4 + +=item 1 + +You cannot declare 2 native methods with the same name in a class (even if they +have different signatures). + +=item 2 + +Native methods can have arguments of any type, but they must return either void +or an Object (use wrappers like Integer and Double to return primitive types). + +=item 3 + +Even if you do not declare them, InlineJavaException and InlineJavaPerlException +exceptions (as well as others) may be thrown from within the native methods + +=back + + +=head1 SEE ALSO + +L<Inline::Java>, L<Inline::Java::Callback>, L<Inline::Java::PerlInterpreter>. + Z<> + + +=head1 AUTHOR + +Patrick LeBoutillier <patl@cpan.org> is the author of Inline::Java. + Z<> + + +=head1 COPYRIGHT + +Copyright (c) 2001-2004, Patrick LeBoutillier. + +All Rights Reserved. This module is free software. It may be used, +redistributed and/or modified under the terms of the Perl Artistic +License. See http://www.perl.com/perl/misc/Artistic.html for more +details. + +=cut diff --git a/Java/PerlNatives/PerlNatives.xs b/Java/PerlNatives/PerlNatives.xs new file mode 100644 index 0000000..c66a5be --- /dev/null +++ b/Java/PerlNatives/PerlNatives.xs @@ -0,0 +1,222 @@ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +/* +#include "stdlib.h" +#include "string.h" +#include "stdio.h" +#include "stdarg.h" +*/ + +#ifdef __CYGWIN__ + #include "w32api/basetyps.h" +#endif + +/* Include the JNI header file */ +#include "jni.h" + + +void throw_ije(JNIEnv *env, char *msg){ + jclass ije ; + + ije = (*(env))->FindClass(env, "org/perl/inline/java/InlineJavaException") ; + if ((*(env))->ExceptionCheck(env)){ + (*(env))->ExceptionDescribe(env) ; + (*(env))->ExceptionClear(env) ; + (*(env))->FatalError(env, "Can't find class InlineJavaException: exiting...") ; + } + (*(env))->ThrowNew(env, ije, msg) ; +} + + +/* + Here we simply check if an exception is pending an re-throw it +*/ +int check_exception_from_java(JNIEnv *env){ + jthrowable exc ; + int ret = 0 ; + + exc = (*(env))->ExceptionOccurred(env) ; + if (exc != NULL){ + /* (*(env))->ExceptionDescribe(env) ; */ + (*(env))->ExceptionClear(env) ; + if ((*(env))->Throw(env, exc)){ + (*(env))->FatalError(env, "Throw of InlineJava*Exception failed: exiting...") ; + } + ret = 1 ; + } + + return ret ; +} + + +jobject create_primitive_object(JNIEnv *env, char f, char *cls_name, jvalue val){ + jclass arg_cls ; + jmethodID mid ; + jobject ret = NULL ; + char sign[64] ; + + arg_cls = (*(env))->FindClass(env, cls_name) ; + if (check_exception_from_java(env)){ + return NULL ; + } + sprintf(sign, "(%c)V", f) ; + mid = (*(env))->GetMethodID(env, arg_cls, "<init>", sign) ; + if (check_exception_from_java(env)){ + return NULL ; + } + ret = (*(env))->NewObjectA(env, arg_cls, mid, &val) ; + if (check_exception_from_java(env)){ + return NULL ; + } + + return ret ; +} + + +jobject extract_va_arg(JNIEnv *env, va_list *list, char f){ + jobject ret = NULL ; + jvalue val ; + + /* + A bit of voodoo going on for J and F, but the rest I think is pretty + kosher (on a 32 bit machine at least...) + */ + switch(f){ + case 'B': + val.b = (jbyte)va_arg(*list, int) ; + ret = create_primitive_object(env, f, "java/lang/Byte", val) ; + break ; + case 'S': + val.s = (jshort)va_arg(*list, int) ; + ret = create_primitive_object(env, f, "java/lang/Short", val) ; + break ; + case 'I': + val.i = (jint)va_arg(*list, int) ; + ret = create_primitive_object(env, f, "java/lang/Integer", val) ; + break ; + case 'J': + val.d = (jdouble)va_arg(*list, double) ; + ret = create_primitive_object(env, f, "java/lang/Long", val) ; + break ; + case 'F': + /* Seems float is not properly promoted to double... */ + val.i = (jint)va_arg(*list, int) ; + ret = create_primitive_object(env, f, "java/lang/Float", val) ; + break ; + case 'D': + val.d = (jdouble)va_arg(*list, double) ; + ret = create_primitive_object(env, f, "java/lang/Double", val) ; + break ; + case 'Z': + val.z = (jboolean)va_arg(*list, int) ; + ret = create_primitive_object(env, f, "java/lang/Boolean", val) ; + break ; + case 'C': + val.c = (jchar)va_arg(*list, int) ; + ret = create_primitive_object(env, f, "java/lang/Character", val) ; + break ; + } + + return ret ; +} + + +/* + This is the generic native function that callback java to call the proper + perl method. +*/ +jobject JNICALL generic_perl_native(JNIEnv *env, jobject obj, ...){ + va_list list ; + jclass cls ; + jmethodID mid ; + jstring jfmt ; + char *fmt ; + int fmt_len ; + jclass obj_cls ; + jobjectArray obj_array ; + jobject arg ; + int i ; + jobject ret = NULL ; + + cls = (*(env))->GetObjectClass(env, obj) ; + mid = (*(env))->GetMethodID(env, cls, "LookupMethod", "()Ljava/lang/String;") ; + if (check_exception_from_java(env)){ + return NULL ; + } + + /* Call obj.LookupMethod to get the format string */ + jfmt = (*(env))->CallObjectMethod(env, obj, mid) ; + if (check_exception_from_java(env)){ + return NULL ; + } + + fmt = (char *)((*(env))->GetStringUTFChars(env, jfmt, NULL)) ; + fmt_len = strlen(fmt) ; + + obj_cls = (*(env))->FindClass(env, "java/lang/Object") ; + if (check_exception_from_java(env)){ + return NULL ; + } + + obj_array = (*(env))->NewObjectArray(env, fmt_len, obj_cls, NULL) ; + if (check_exception_from_java(env)){ + return NULL ; + } + + (*(env))->SetObjectArrayElement(env, obj_array, 0, obj) ; + if (check_exception_from_java(env)){ + return NULL ; + } + va_start(list, obj) ; + for (i = 1 ; i < fmt_len ; i++){ + if (fmt[i] != 'L'){ + arg = extract_va_arg(env, &list, fmt[i]) ; + if (arg == NULL){ + return NULL ; + } + } + else{ + arg = (jobject)va_arg(list, jobject) ; + } + (*(env))->SetObjectArrayElement(env, obj_array, i, arg) ; + if (check_exception_from_java(env)){ + return NULL ; + } + } + va_end(list) ; + + /* Call obj.InvokePerlMethod and grab the returned object and return it */ + mid = (*(env))->GetMethodID(env, cls, "InvokePerlMethod", "([Ljava/lang/Object;)Ljava/lang/Object;") ; + if (check_exception_from_java(env)){ + return NULL ; + } + + ret = (*(env))->CallObjectMethod(env, obj, mid, obj_array) ; + if (check_exception_from_java(env)){ + return NULL ; + } + + return ret ; +} + + +/* + This function is used to register the specified native method and associate it with our magic + method that trap and redirects all the Perl native calls. +*/ +JNIEXPORT void JNICALL Java_org_perl_inline_java_InlineJavaPerlNatives_RegisterMethod(JNIEnv *env, jobject obj, jclass cls, jstring name, jstring signature){ + JNINativeMethod nm ; + + /* Register the function */ + nm.name = (char *)((*(env))->GetStringUTFChars(env, name, NULL)) ; + nm.signature = (char *)((*(env))->GetStringUTFChars(env, signature, NULL)) ; + nm.fnPtr = generic_perl_native ; + + (*(env))->RegisterNatives(env, cls, &nm, 1) ; + (*(env))->ReleaseStringUTFChars(env, name, nm.name) ; + (*(env))->ReleaseStringUTFChars(env, signature, nm.signature) ; + if (check_exception_from_java(env)){ + return ; + } +} diff --git a/Java/PerlNatives/t/01_init.t b/Java/PerlNatives/t/01_init.t new file mode 100644 index 0000000..d889213 --- /dev/null +++ b/Java/PerlNatives/t/01_init.t @@ -0,0 +1,10 @@ +use strict ; +use Test ; + +BEGIN { + plan(tests => 1) ; + mkdir('./_Inline_test', 0777) unless -e './_Inline_test' ; +} + + +ok(1) ; diff --git a/Java/PerlNatives/t/02_perl_natives.t b/Java/PerlNatives/t/02_perl_natives.t new file mode 100644 index 0000000..7b6932c --- /dev/null +++ b/Java/PerlNatives/t/02_perl_natives.t @@ -0,0 +1,118 @@ +use strict ; +use Test ; + + +BEGIN { + plan(tests => 5) ; +} + + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline::Java qw(caught) ; + +use Inline ( + Java => 'DATA', +) ; + + +eval { + t121->init() ; + my $t = new t121() ; + ok($t->types_stub(1, 2, 3, 4, 5, 6, 1, 2, "1000"), 1024) ; + ok($t->array_stub([34, 56], ["toto", "789"]), 789 + 34) ; + + my $t2 = new t1212() ; + ok($t2->types_stub(1, 2, 3, 4, 5, 6, 1, 2, "1000"), 1024) ; + + ok($t->callback_stub(), "toto") ; + ok($t->__get_private()->{proto}->ObjectCount(), 2) ; +} ; +if ($@){ + if (caught("java.lang.Throwable")){ + $@->printStackTrace() ; + die("Caught Java Exception") ; + } + else{ + die $@ ; + } +} + + +################################## + +package t121 ; +sub types { + my $this = shift ; + + my $sum = 0 ; + map {$sum += $_} @_ ; + return $sum ; +} + + +sub array { + my $this = shift ; + my $i = shift ; + my $str = shift ; + + return $i->[0] + $str->[1] ; +} + + +sub callback { + my $this = shift ; + + return $this->get_name() ; +} + + +package main ; +__DATA__ + +__Java__ + + +import java.io.* ; +import org.perl.inline.java.* ; + +class t121 extends InlineJavaPerlNatives { + static public boolean got14(){ + return System.getProperty("java.version").startsWith("1.4") ; + } + + public t121() throws InlineJavaException { + } + + static public void init() throws InlineJavaException { + init("test") ; + } + + public String types_stub(byte b, short s, int i, long j, float f, double d, + boolean x, char c, String str){ + return types(b, s, i, j, f, d, x, c, str) ; + } + public native String types(byte b, short s, int i, long j, float f, double d, + boolean x, char c, String str) ; + + public String array_stub(int i[], String str[]){ + return array(i, str) ; + } + private native String array(int i[], String str[]) ; + + public String callback_stub(){ + return callback() ; + } + public native String callback() ; + + public String get_name(){ + return "toto" ; + } +} ; + + +class t1212 extends t121 { + public t1212() throws InlineJavaException { + } +} ; diff --git a/Java/Portable.pm b/Java/Portable.pm new file mode 100644 index 0000000..c3f0a96 --- /dev/null +++ b/Java/Portable.pm @@ -0,0 +1,267 @@ +package Inline::Java::Portable ; +@Inline::Java::Portable::ISA = qw(Exporter) ; + + +use strict ; +use Exporter ; +use Carp ; +use Config ; +use File::Find ; +use File::Spec ; + +$Inline::Java::Portable::VERSION = '0.53' ; + +# Here is some code to figure out if we are running on command.com +# shell under Windows. +my $COMMAND_COM = + ( + ($^O eq 'MSWin32')&& + ( + ($ENV{PERL_INLINE_JAVA_COMMAND_COM})|| + ( + (defined($ENV{COMSPEC}))&& + ($ENV{COMSPEC} =~ /(command|4dos)\.com/i) + )|| + (`ver` =~ /win(dows )?((9[58])|(m[ei]))/i) + ) + ) || 0 ; + + +sub debug { + if (Inline::Java->can("debug")){ + return Inline::Java::debug(@_) ; + } +} + + +# Cleans the CLASSPATH environment variable and adds +# the paths specified. +sub make_classpath { + my @paths = @_ ; + + my @list = () ; + if (defined($ENV{CLASSPATH})){ + push @list, $ENV{CLASSPATH} ; + } + push @list, @paths ; + + my $sep = Inline::Java::Portable::portable("ENV_VAR_PATH_SEP_CP") ; + my @cp = split(/$sep+/, join($sep, @list)) ; + + # Clean up paths + foreach my $p (@cp){ + $p =~ s/^\s+// ; + $p =~ s/\s+$// ; + } + + # Remove duplicates, remove invalids but preserve order + my @fcp = () ; + my %cp = map {$_ => 1} @cp ; + foreach my $p (@cp){ + if (($p)&&(-e $p)){ + if ($cp{$p}){ + my $fp = File::Spec->rel2abs($p) ; + push @fcp, Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", $fp) ; + delete $cp{$p} ; + } + } + else{ + Inline::Java::debug(2, "classpath candidate '$p' scraped") ; + } + } + + my $cp = join($sep, @fcp) ; + + return (wantarray ? @fcp : $cp) ; +} + + +sub get_jar_dir { + my $path = $INC{"Inline/Java.pm"} ; + my ($v, $d, $f) = File::Spec->splitpath($path) ; + + # This undef for the file should be ok. + my $dir = File::Spec->catpath($v, $d, 'Java', undef) ; + + return File::Spec->rel2abs($dir) ; +} + + +sub get_server_jar { + return File::Spec->catfile(get_jar_dir(), 'InlineJavaServer.jar') ; +} + + +sub get_user_jar { + return File::Spec->catfile(get_jar_dir(), 'InlineJavaUser.jar') ; +} + + +sub get_source_dir { + return File::Spec->catdir(get_jar_dir(), 'sources') ; +} + + +# This maybe could be made more stable +sub find_classes_in_dir { + my $dir = shift ; + + my @ret = () ; + find(sub { + my $f = $_ ; + if ($f =~ /\.class$/){ + my $file = $File::Find::name ; + my $fdir = $File::Find::dir ; + my @dirs = File::Spec->splitdir($fdir) ; + # Remove '.' + shift @dirs ; + # Add an empty dir to get the last '.' (for '.class') + if ((! scalar(@dirs))||($dirs[-1] ne '')){ + push @dirs, '' ; + } + my $pkg = (scalar(@dirs) ? join('.', @dirs) : '') ; + my $class = "$pkg$f" ; + $class =~ s/\.class$// ; + push @ret, {file => $file, class => $class} ; + } + }, $dir) ; + + return @ret ; +} + + + +my $map = { + _DEFAULT_ => { + EXE_EXTENSION => $Config{exe_ext}, + GOT_ALARM => $Config{d_alarm} || 0, + GOT_FORK => $Config{d_fork} || 0, + GOT_NEXT_FREE_PORT => 1, + GOT_SYMLINK => 1, + GOT_SAFE_SIGNALS => 1, + ENV_VAR_PATH_SEP => $Config{path_sep}, + SO_EXT => $Config{dlext}, + PREFIX => $Config{prefix}, + LIBPERL => $Config{libperl}, + DETACH_OK => 1, + SO_LIB_PATH_VAR => $Config{ldlibpthname}, + ENV_VAR_PATH_SEP_CP => ':', + IO_REDIR => '2>&1', + MAKE => 'make', + DEV_NULL => '/dev/null', + COMMAND_COM => 0, + SUB_FIX_JAVA_PATH => undef, + SUB_FIX_CMD_QUOTES => undef, + SUB_FIX_MAKE_QUOTES => undef, + JVM_LIB => "libjvm.$Config{dlext}", + JVM_SO => "libjvm.$Config{dlext}", + PRE_WHOLE_ARCHIVE => '-Wl,--whole-archive', + POST_WHOLE_ARCHIVE => '-Wl,--no-whole-archive', + PERL_PARSE_DUP_ENV => '-DPERL_PARSE_DUP_ENV', + BUILD_JNI_BY_DFLT => 1, + J2SDK_BIN => 'bin', + DEFAULT_J2SDK_DIR => undef, + OTHERLDFLAGS => '', + dynamic_lib => {} + }, + MSWin32 => { + ENV_VAR_PATH_SEP_CP => ';', + # 2>&1 doesn't work under command.com + IO_REDIR => ($COMMAND_COM ? '' : undef), + MAKE => 'nmake', + DEV_NULL => 'nul', + COMMAND_COM => $COMMAND_COM, + SUB_FIX_MAKE_QUOTES => sub { + my $arg = shift ; + $arg = qq{"$arg"} ; + return $arg ; + }, + SO_LIB_PATH_VAR => 'PATH', + DETACH_OK => 0, + JVM_LIB => 'jvm.lib', + JVM_SO => 'jvm.dll', + GOT_NEXT_FREE_PORT => 0, + GOT_SYMLINK => 0, + GOT_SAFE_SIGNALS => 0, + PRE_WHOLE_ARCHIVE => '', + POST_WHOLE_ARCHIVE => '', + }, + cygwin => { + ENV_VAR_PATH_SEP_CP => ';', + SUB_FIX_JAVA_PATH => sub { + my $arg = shift ; + if (defined($arg)&&($arg)){ + $arg = `cygpath -w \"$arg\"` ; + chomp($arg) ; + } + return $arg ; + }, + JVM_LIB => 'jvm.lib', + JVM_SO => 'jvm.dll', + BUILD_JNI_BY_DFLT => 1, + dynamic_lib => { OTHERLDFLAGS => '-Wl,-add-stdcall-alias' }, + }, + hpux => { + GOT_NEXT_FREE_PORT => 0, + }, + solaris => { + GOT_NEXT_FREE_PORT => 0, + PRE_WHOLE_ARCHIVE => '-Wl,-zallextract', + POST_WHOLE_ARCHIVE => '-Wl,-zdefaultextract', + }, + aix => { + JVM_LIB => "libjvm$Config{lib_ext}", + JVM_SO => "libjvm$Config{lib_ext}", + }, + darwin => { + # Suggested by Ken Williams, mailing list 2004/07/07 + SO_EXT => $Config{so}, + # Andrew Bruno + JVM_LIB => "libjvm.dylib", + JVM_SO => "libjvm.dylib", + PRE_WHOLE_ARCHIVE => '-Wl', + POST_WHOLE_ARCHIVE => '-Wl', + GOT_SYMLINK => 1, + J2SDK_BIN => 'Commands', + DEFAULT_J2SDK_DIR => '/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK', + # Tim Bunce: + OTHERLDFLAGS => '-framework JavaVM', + }, +} ; + +sub portable { + my $key = shift ; + my $arg = shift ; + + if (! exists($map->{_DEFAULT_}->{$key})){ + croak "Portability issue $key not defined!" ; + } + + my $val = undef ; + if ((defined($map->{$^O}))&&(defined($map->{$^O}->{$key}))){ + $val = $map->{$^O}->{$key} ; + } + else { + $val = $map->{_DEFAULT_}->{$key} ; + } + + if ($key =~ /^SUB_/){ + my $sub = $val ; + if (defined($sub)){ + $arg = $sub->($arg) ; + Inline::Java::Portable::debug(4, "portable: $key => $arg for $^O is '$arg'") ; + return $arg ; + } + else { + Inline::Java::Portable::debug(4, "portable: $key => $arg for $^O is default '$arg'") ; + return $arg ; + } + } + else { + Inline::Java::Portable::debug(4, "portable: $key for $^O is '$val'") ; + return $val ; + } +} + + +1 ; diff --git a/Java/Protocol.pm b/Java/Protocol.pm new file mode 100644 index 0000000..56e715c --- /dev/null +++ b/Java/Protocol.pm @@ -0,0 +1,612 @@ +package Inline::Java::Protocol ; + +use strict ; +use Inline::Java::Object ; +use Inline::Java::Array ; +use Carp ; +use MIME::Base64 ; +BEGIN { + eval "require Encode" ; +} + + +$Inline::Java::Protocol::VERSION = '0.53' ; + +my %CLASSPATH_ENTRIES = () ; + + +sub new { + my $class = shift ; + my $obj = shift ; + my $inline = shift ; + + my $this = {} ; + $this->{obj_priv} = $obj || {} ; + $this->{inline} = $inline ; + + bless($this, $class) ; + return $this ; +} + + +sub AddClassPath { + my $this = shift ; + my @paths = @_ ; + + @paths = map { + my $e = $_ ; + if ($CLASSPATH_ENTRIES{$e}){ + () ; + } + else{ + Inline::Java::debug(2, "adding to classpath: '$e'") ; + $CLASSPATH_ENTRIES{$e} = 1 ; + $e ; + } + } @paths ; + + my $data = "add_classpath " . join(" ", map {encode($_)} @paths) ; + + return $this->Send($data) ; +} + + +sub ServerType { + my $this = shift ; + + Inline::Java::debug(3, "getting server type") ; + + my $data = "server_type" ; + + return $this->Send($data) ; +} + + +# Known issue: $classes must contain at least one class name. +sub Report { + my $this = shift ; + my $classes = shift ; + + Inline::Java::debug(3, "reporting on $classes") ; + + my $data = join(" ", + "report", + $this->ValidateArgs([$classes]), + ) ; + + return $this->Send($data) ; +} + + +sub ISA { + my $this = shift ; + my $proto = shift ; + + my $class = $this->{obj_priv}->{java_class} ; + + return $this->__ISA($proto, $class) ; +} + + +sub __ISA { + my $this = shift ; + my $proto = shift ; + my $class = shift ; + + Inline::Java::debug(3, "checking if $class is a $proto") ; + + my $data = join(" ", + "isa", + Inline::Java::Class::ValidateClass($class), + Inline::Java::Class::ValidateClass($proto), + ) ; + + return $this->Send($data) ; +} + + +sub ObjectCount { + my $this = shift ; + + Inline::Java::debug(3, "getting object count") ; + + my $data = join(" ", + "obj_cnt", + ) ; + + return $this->Send($data) ; +} + + +# Called to create a Java object +sub CreateJavaObject { + my $this = shift ; + my $class = shift ; + my $proto = shift ; + my $args = shift ; + + Inline::Java::debug(3, "creating object new $class" . $this->CreateSignature($args)) ; + + my $data = join(" ", + "create_object", + Inline::Java::Class::ValidateClass($class), + $this->CreateSignature($proto, ","), + $this->ValidateArgs($args), + ) ; + + return $this->Send($data, 1) ; +} + + +# Calls a Java method. +sub CallJavaMethod { + my $this = shift ; + my $method = shift ; + my $proto = shift ; + my $args = shift ; + + my $id = $this->{obj_priv}->{id} ; + my $class = $this->{obj_priv}->{java_class} ; + Inline::Java::debug(3, "calling object($id).$method" . $this->CreateSignature($args)) ; + + my $data = join(" ", + "call_method", + $id, + Inline::Java::Class::ValidateClass($class), + $this->ValidateMethod($method), + $this->CreateSignature($proto, ","), + $this->ValidateArgs($args), + ) ; + + return $this->Send($data) ; +} + + +# Casts a Java object. +sub Cast { + my $this = shift ; + my $class = shift ; + + my $id = $this->{obj_priv}->{id} ; + Inline::Java::debug(3, "creating a new reference to object($id) with type $class") ; + + my $data = join(" ", + "cast", + $id, + Inline::Java::Class::ValidateClass($class), + ) ; + + return $this->Send($data) ; +} + + +# Sets a member variable. +sub SetJavaMember { + my $this = shift ; + my $member = shift ; + my $proto = shift ; + my $arg = shift ; + + my $id = $this->{obj_priv}->{id} ; + my $class = $this->{obj_priv}->{java_class} ; + Inline::Java::debug(3, "setting object($id)->{$member} = " . ($arg->[0] || '')) ; + my $data = join(" ", + "set_member", + $id, + Inline::Java::Class::ValidateClass($class), + $this->ValidateMember($member), + Inline::Java::Class::ValidateClass($proto->[0]), + $this->ValidateArgs($arg), + ) ; + + return $this->Send($data) ; +} + + +# Gets a member variable. +sub GetJavaMember { + my $this = shift ; + my $member = shift ; + my $proto = shift ; + + my $id = $this->{obj_priv}->{id} ; + my $class = $this->{obj_priv}->{java_class} ; + Inline::Java::debug(3, "getting object($id)->{$member}") ; + + my $data = join(" ", + "get_member", + $id, + Inline::Java::Class::ValidateClass($class), + $this->ValidateMember($member), + Inline::Java::Class::ValidateClass($proto->[0]), + "undef:", + ) ; + + return $this->Send($data) ; +} + + +sub ReadFromJavaHandle { + my $this = shift ; + my $len = shift ; + + my $id = $this->{obj_priv}->{id} ; + my $class = $this->{obj_priv}->{java_class} ; + Inline::Java::debug(3, "reading from handle object($id)") ; + + my $data = join(" ", + "read", + $id, + $len, + ) ; + + return $this->Send($data) ; +} + + +sub MakeJavaHandleBuffered { + my $this = shift ; + + my $id = $this->{obj_priv}->{id} ; + Inline::Java::debug(3, "making handle object($id) buffered") ; + + my $data = join(" ", + "make_buffered", + $id, + ) ; + + return $this->Send($data) ; +} + + +sub ReadLineFromJavaHandle { + my $this = shift ; + + my $id = undef ; + if (! defined($this->{obj_priv}->{buffered})){ + my $nid = $this->MakeJavaHandleBuffered() ; + $this->{obj_priv}->{buffered} = Inline::Java::Object->__new('<buffer>', $this->{inline}, $nid) ; + } + $id = $this->{obj_priv}->{buffered}->__get_private()->{id} ; + Inline::Java::debug(3, "reading line from handle object($id)") ; + + my $data = join(" ", + "readline", + $id, + ) ; + + return $this->Send($data) ; +} + + +sub WriteToJavaHandle { + my $this = shift ; + my $str = shift ; + + my $id = $this->{obj_priv}->{id} ; + Inline::Java::debug(3, "writing to handle object($id)") ; + + my $data = join(" ", + "write", + $id, + $this->ValidateArgs([$str]), + ) ; + + return $this->Send($data) ; +} + + +sub CloseJavaHandle { + my $this = shift ; + + my $id = $this->{obj_priv}->{id} ; + my $class = $this->{obj_priv}->{java_class} ; + Inline::Java::debug(3, "closing handle object($id)") ; + + my $data = join(" ", + "close", + $id, + ) ; + + return $this->Send($data) ; +} + + +# Deletes a Java object +sub DeleteJavaObject { + my $this = shift ; + my $obj = shift ; + + if (defined($this->{obj_priv}->{id})){ + my $id = $this->{obj_priv}->{id} ; + my $class = $this->{obj_priv}->{java_class} ; + + Inline::Java::debug(3, "deleting object $obj $id ($class)") ; + + my $data = join(" ", + "delete_object", + $id, + ) ; + + $this->Send($data) ; + } +} + + +# This method makes sure that the method we are asking for +# has the correct form for a Java method. +sub ValidateMethod { + my $this = shift ; + my $method = shift ; + + if ($method !~ /^(\w+)$/){ + croak "Invalid Java method name $method" ; + } + + return $method ; +} + + +# This method makes sure that the member we are asking for +# has the correct form for a Java member. +sub ValidateMember { + my $this = shift ; + my $member = shift ; + + if ($member !~ /^(\w+)$/){ + croak "Invalid Java member name $member" ; + } + + return $member ; +} + + +# Validates the arguments to be used in a method call. +sub ValidateArgs { + my $this = shift ; + my $args = shift ; + my $callback = shift ; + + my @ret = () ; + foreach my $arg (@{$args}){ + if (! defined($arg)){ + push @ret, "undef:" ; + } + elsif (ref($arg)){ + if ((UNIVERSAL::isa($arg, "Inline::Java::Object"))||(UNIVERSAL::isa($arg, "Inline::Java::Array"))){ + my $obj = $arg ; + if (UNIVERSAL::isa($arg, "Inline::Java::Array")){ + $obj = $arg->__get_object() ; + } + my $class = $obj->__get_private()->{java_class} ; + my $id = $obj->__get_private()->{id} ; + push @ret, "java_object:$class:$id" ; + } + elsif (UNIVERSAL::isa($arg, 'Inline::Java::double')){ + push @ret, "double:" . encode(${$arg}) ; + } + elsif ($arg =~ /^(.*?)=/){ + my $id = Inline::Java::Callback::PutObject($arg) ; + # Bug. The delimiter is :, so we need to escape the package separator (::) + my $pkg = $1 ; + $pkg =~ s/:/\//g ; + push @ret, "perl_object:$pkg:$id" ; + } + else { + if (! $callback){ + croak "A Java method or member can only have Java objects, Java arrays, Perl objects or scalars as arguments" ; + } + else{ + croak "A Java callback function can only return Java objects, Java arrays, Perl objects or scalars" ; + } + } + } + else { + push @ret, "scalar:" . encode($arg) ; + } + } + + return @ret ; +} + + +sub CreateSignature { + my $this = shift ; + my $proto = shift ; + my $del = shift || ", " ; + + no warnings ; + my $sig = join($del, @{$proto}) ; + return "($sig)" ; +} + + +# This actually sends the request to the Java program. It also takes +# care of registering the returned object (if any) +sub Send { + my $this = shift ; + my $data = shift ; + my $const = shift ; + + my $resp = Inline::Java::__get_JVM()->process_command($this->{inline}, $data) ; + if ($resp =~ /^error scalar:([\w+\/=+]*)$/){ + my $msg = decode($1) ; + Inline::Java::debug(3, "packet recv error: $msg") ; + croak $msg ; + } + elsif ($resp =~ s/^ok //){ + return $this->DeserializeObject($const, $resp) ; + } + + croak "Malformed response from server: $resp" ; +} + + +sub DeserializeObject { + my $this = shift ; + my $const = shift ; + my $resp = shift ; + + if ($resp =~ /^scalar:([\w+\/=+]*)$/){ + return decode($1) ; + } + elsif ($resp =~ /^double:(.*)$/){ + my $bytes = decode($1) ; + my $d = unpack('d', $bytes) ; + return $d ; + } + elsif ($resp =~ /^undef:$/){ + return undef ; + } + elsif ($resp =~ /^java_(object|array|handle):([01]):(\d+):(.*)$/){ + # Create the Perl object wrapper and return it. + my $type = $1 ; + my $thrown = $2 ; + my $id = $3 ; + my $class = $4 ; + + if ($thrown){ + # If we receive a thrown object, we jump out of 'constructor + # mode' and process the returned object. + $const = 0 ; + } + + if ($const){ + $this->{obj_priv}->{java_class} = $class ; + $this->{obj_priv}->{id} = $id ; + + return undef ; + } + else { + my $pkg = $this->{inline}->get_api('pkg') ; + + my $obj = undef ; + my $elem_class = $class ; + + Inline::Java::debug(3, "checking if stub is array...") ; + if ($type eq 'array'){ + my @d = Inline::Java::Class::ValidateClassSplit($class) ; + $elem_class = $d[2] ; + } + + my $perl_class = "Inline::Java::Object" ; + if ($elem_class){ + # We have a real class or an array of real classes + $perl_class = Inline::Java::java2perl($pkg, $elem_class) ; + if (Inline::Java::Class::ClassIsReference($elem_class)){ + if (! Inline::Java::known_to_perl($pkg, $elem_class)){ + if (($thrown)||($this->{inline}->get_java_config('AUTOSTUDY'))){ + Inline::Java::debug(2, "autostudying $elem_class...") ; + $this->{inline}->_study([$elem_class]) ; + } + else{ + # Object is not known to Perl, it lives as a + # Inline::Java::Object + $perl_class = "Inline::Java::Object" ; + } + } + } + } + else{ + # We should only get here if an array of primitives types + # was returned, and there is nothing to do since + # the block below will handle it. + } + + if ($type eq 'array'){ + Inline::Java::debug(3, "creating array object...") ; + $obj = Inline::Java::Object->__new($class, $this->{inline}, $id) ; + $obj = new Inline::Java::Array($obj) ; + Inline::Java::debug(3, "array object created...") ; + } + # To be finished at a later time... + # elsif ($type eq 'handle'){ + # Inline::Java::debug(3, "creating handle object...") ; + # $obj = Inline::Java::Object->__new($class, $this->{inline}, $id) ; + # $obj = new Inline::Java::Handle($obj) ; + # Inline::Java::debug(3, "handle object created...") ; + # } + else{ + $obj = $perl_class->__new($class, $this->{inline}, $id) ; + } + + if ($thrown){ + Inline::Java::debug(3, "throwing stub...") ; + my ($msg, $score) = $obj->__isa('org.perl.inline.java.InlineJavaPerlException') ; + if ($msg){ + die $obj ; + } + else{ + die $obj->GetObject() ; + } + } + else{ + Inline::Java::debug(3, "returning stub...") ; + return $obj ; + } + } + } + elsif ($resp =~ /^perl_object:(\d+):(.*)$/){ + my $id = $1 ; + my $pkg = $2 ; + + return Inline::Java::Callback::GetObject($id) ; + } + else{ + croak "Malformed response from server: $resp" ; + } +} + + +sub encode { + my $s = shift ; + + # Get UTF-8 byte representation of the data. + my $bytes = undef ; + if ($INC{'Encode.pm'}){ + $bytes = Encode::encode_utf8($s) ; + } + else { + $bytes = $s ; + $bytes =~ s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg ; + } + + # Base-64 encode it. + my $base64 = encode_base64($bytes, '') ; + + return $base64 ; +} + + +sub decode { + my $s = shift ; + + # Decode the Base-64 data into bytes (UTF-8 representation of the data). + my $bytes = decode_base64($s) ; + + # Take the UTF-8 encoding and convert it back to logical characters. + my $string = undef ; + if ($INC{'Encode.pm'}){ + $string = Encode::decode_utf8($bytes) ; + } + else { + $string = $bytes ; + $string =~ s/([\xC2\xC3])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg ; + } + + if (utf8->can('downgrade')){ + utf8::downgrade($string, 1) ; + } + + return $string ; +} + + +sub DESTROY { + my $this = shift ; + + Inline::Java::debug(4, "destroying Inline::Java::Protocol") ; +} + + +1 ; diff --git a/Java/Server.pm b/Java/Server.pm new file mode 100644 index 0000000..59ad7a2 --- /dev/null +++ b/Java/Server.pm @@ -0,0 +1,130 @@ +package Inline::Java::Server ; +@Inline::Java::Server::ISA = qw(Exporter) ; + +# Export the cast function if wanted +@EXPORT_OK = qw(start stop status) ; + + +use strict ; +use Exporter ; +use Carp ; +require Inline ; +require Inline::Java ; +use File::Spec ; + + +$Inline::Java::Server::VERSION = '0.53' ; + + +# Create a dummy Inline::Java object in order to +# get the default options. +my $IJ = bless({}, "Inline::Java") ; +$IJ->validate( + SHARED_JVM => 1 +) ; + + + +sub import { + my $class = shift ; + my $a = shift ; + + my @actions = () ; + if ($a eq 'restart'){ + push @actions, 'stop', 'sleep', 'start' ; + } + else{ + push @actions, $a ; + } + + my $host = $IJ->get_java_config("HOST") ; + my $bind = $IJ->get_java_config("BIND") ; + my $port = $IJ->get_java_config("PORT") ; + foreach $a (@actions){ + if ($a eq 'sleep'){ + sleep(5) ; + next ; + } + + my $status = Inline::Java::Server::status() ; + + if ($a eq 'start'){ + if ($status){ + print "SHARED_JVM server on port $bind:$port is already running\n" ; + } + else{ + Inline::Java::Server::start() ; + my $pid = Inline::Java::__get_JVM()->{pid} ; + print "SHARED_JVM server on port $bind:$port started with pid $pid\n" ; + } + } + elsif ($a eq 'stop'){ + if (! $status){ + print "SHARED_JVM server on port $host:$port is not running\n" ; + } + else { + Inline::Java::Server::stop() ; + print "SHARED_JVM server on port $host:$port stopped\n" ; + } + } + elsif ($a eq 'status'){ + if ($status){ + print "SHARED_JVM on port $host:$port is running\n" ; + } + else { + print "SHARED_JVM on port $host:$port is not running\n" ; + } + } + else{ + croak("Usage: perl -MInline::Java::Server=(start|stop|restart|status)\n") ; + } + } + + exit() ; +} + + + +sub status { + my $socket = undef ; + + eval { + $socket = Inline::Java::JVM::setup_socket( + $IJ->get_java_config("HOST"), + $IJ->get_java_config("PORT"), + 0, + 1 + ) ; + } ; + if ($@){ + return 0 ; + } + else { + close($socket) ; + return 1 ; + } +} + + +sub start { + my $dir = $ENV{PERL_INLINE_JAVA_DIRECTORY} ; + + Inline->bind( + Java => 'STUDY', + SHARED_JVM => 1, + ($dir ? (DIRECTORY => $dir) : ()), + ) ; +} + + +sub stop { + # This will connect us to the running JVM + Inline::Java::Server::start() ; + Inline::Java::capture_JVM() ; + Inline::Java::shutdown_JVM() ; +} + + + +1 ; + diff --git a/Java/jvm.def b/Java/jvm.def new file mode 100644 index 0000000..bdab35d --- /dev/null +++ b/Java/jvm.def @@ -0,0 +1,4 @@ +EXPORTS +JNI_CreateJavaVM@12 +JNI_GetDefaultJavaVMInitArgs@4 +JNI_GetCreatedJavaVMs@12 diff --git a/Java/sources/org/perl/inline/java/InlineJavaArray.java b/Java/sources/org/perl/inline/java/InlineJavaArray.java new file mode 100644 index 0000000..c397ab4 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaArray.java @@ -0,0 +1,100 @@ +package org.perl.inline.java ; + +import java.util.* ; +import java.lang.reflect.Array ; + + +class InlineJavaArray { + private InlineJavaClass ijc ; + + + InlineJavaArray(InlineJavaClass _ijc){ + ijc = _ijc ; + } + + + Object CreateArray(Class c, StringTokenizer st) throws InlineJavaException { + StringBuffer sb = new StringBuffer(st.nextToken()) ; + sb.replace(0, 1, "") ; + sb.replace(sb.length() - 1, sb.length(), "") ; + + StringTokenizer st2 = new StringTokenizer(sb.toString(), ",") ; + ArrayList al = new ArrayList() ; + while (st2.hasMoreTokens()){ + al.add(al.size(), st2.nextToken()) ; + } + + int size = al.size() ; + int dims[] = new int[size] ; + for (int i = 0 ; i < size ; i++){ + dims[i] = Integer.parseInt((String)al.get(i)) ; + InlineJavaUtils.debug(4, "array dimension: " + (String)al.get(i)) ; + } + + Object array = null ; + try { + array = Array.newInstance(c, dims) ; + + ArrayList args = new ArrayList() ; + while (st.hasMoreTokens()){ + args.add(args.size(), st.nextToken()) ; + } + + // Now we need to fill it. Since we have an arbitrary number + // of dimensions, we can do this recursively. + + PopulateArray(array, c, dims, args) ; + } + catch (IllegalArgumentException e){ + throw new InlineJavaException("Arguments to array constructor for class " + c.getName() + " are incompatible: " + e.getMessage()) ; + } + + return array ; + } + + + void PopulateArray (Object array, Class elem, int dims[], ArrayList args) throws InlineJavaException { + if (dims.length > 1){ + int nb_args = args.size() ; + int nb_sub_dims = dims[0] ; + int nb_args_per_sub_dim = nb_args / nb_sub_dims ; + + int sub_dims[] = new int[dims.length - 1] ; + for (int i = 1 ; i < dims.length ; i++){ + sub_dims[i - 1] = dims[i] ; + } + + for (int i = 0 ; i < nb_sub_dims ; i++){ + // We want the args from i*nb_args_per_sub_dim -> + ArrayList sub_args = new ArrayList() ; + for (int j = (i * nb_args_per_sub_dim) ; j < ((i + 1) * nb_args_per_sub_dim) ; j++){ + sub_args.add(sub_args.size(), (String)args.get(j)) ; + } + PopulateArray(((Object [])array)[i], elem, sub_dims, sub_args) ; + } + } + else{ + String msg = "In creation of array of " + elem.getName() + ": " ; + try { + for (int i = 0 ; i < dims[0] ; i++){ + String arg = (String)args.get(i) ; + + Object o = ijc.CastArgument(elem, arg) ; + Array.set(array, i, o) ; + if (o != null){ + InlineJavaUtils.debug(4, "setting array element " + String.valueOf(i) + " to " + o.toString()) ; + } + else{ + InlineJavaUtils.debug(4, "setting array element " + String.valueOf(i) + " to " + o) ; + } + } + } + catch (InlineJavaCastException e){ + throw new InlineJavaCastException(msg + e.getMessage()) ; + } + catch (InlineJavaException e){ + throw new InlineJavaException(msg + e.getMessage()) ; + } + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaCallback.java b/Java/sources/org/perl/inline/java/InlineJavaCallback.java new file mode 100644 index 0000000..da23afe --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaCallback.java @@ -0,0 +1,158 @@ +package org.perl.inline.java ; + +import java.util.* ; +import java.io.* ; + + +/* + Callback to Perl... +*/ +class InlineJavaCallback { + private InlineJavaServer ijs = InlineJavaServer.GetInstance() ; + private String pkg = null ; + private InlineJavaPerlObject obj = null ; + private String method = null ; + private Object args[] = null ; + private Class cast = null ; + private Object response = null ; + private boolean response_set = false ; + + + InlineJavaCallback(String _pkg, String _method, Object _args[], Class _cast) { + this(null, _pkg, _method, _args, _cast) ; + } + + + InlineJavaCallback(InlineJavaPerlObject _obj, String _method, Object _args[], Class _cast) { + this(_obj, null, _method, _args, _cast) ; + if (obj == null){ + throw new NullPointerException() ; + } + } + + + private InlineJavaCallback(InlineJavaPerlObject _obj, String _pkg, String _method, Object _args[], Class _cast) { + obj = _obj ; + pkg = _pkg ; + method = _method ; + args = _args ; + cast = _cast ; + + if (method == null){ + throw new NullPointerException() ; + } + if (cast == null){ + cast = java.lang.Object.class ; + } + } + + + private String GetCommand(InlineJavaProtocol ijp) throws InlineJavaException { + String via = null ; + if (obj != null){ + via = "" + obj.GetId() ; + } + else if (pkg != null){ + via = pkg ; + } + StringBuffer cmdb = new StringBuffer("callback " + via + " " + method + " " + cast.getName()) ; + if (args != null){ + for (int i = 0 ; i < args.length ; i++){ + cmdb.append(" " + ijp.SerializeObject(args[i], null)) ; + } + } + return cmdb.toString() ; + } + + + void ClearResponse(){ + response = null ; + response_set = false ; + } + + + Object GetResponse(){ + return response ; + } + + + synchronized Object WaitForResponse(Thread t){ + while (! response_set){ + try { + InlineJavaUtils.debug(3, "waiting for callback response in " + t.getName() + "...") ; + wait() ; + } + catch (InterruptedException ie){ + // Do nothing, return and wait() some more... + } + } + InlineJavaUtils.debug(3, "got callback response") ; + Object resp = response ; + response = null ; + response_set = false ; + return resp ; + } + + + synchronized void NotifyOfResponse(Thread t){ + InlineJavaUtils.debug(3, "notifying that callback has completed in " + t.getName()) ; + notify() ; + } + + + synchronized void Process() throws InlineJavaException, InlineJavaPerlException { + Object ret = null ; + try { + InlineJavaProtocol ijp = new InlineJavaProtocol(ijs, null) ; + String cmd = GetCommand(ijp) ; + InlineJavaUtils.debug(2, "callback command: " + cmd) ; + + Thread t = Thread.currentThread() ; + String resp = null ; + while (true) { + InlineJavaUtils.debug(3, "packet sent (callback) is " + cmd) ; + if (! ijs.IsJNI()){ + // Client-server mode. + InlineJavaServerThread ijt = (InlineJavaServerThread)t ; + ijt.GetWriter().write(cmd + "\n") ; + ijt.GetWriter().flush() ; + + resp = ijt.GetReader().readLine() ; + } + else{ + // JNI mode + resp = ijs.jni_callback(cmd) ; + } + InlineJavaUtils.debug(3, "packet recv (callback) is " + resp) ; + + StringTokenizer st = new StringTokenizer(resp, " ") ; + String c = st.nextToken() ; + if (c.equals("callback")){ + boolean thrown = new Boolean(st.nextToken()).booleanValue() ; + String arg = st.nextToken() ; + InlineJavaClass ijc = new InlineJavaClass(ijs, ijp) ; + ret = ijc.CastArgument(cast, arg) ; + + if (thrown){ + throw new InlineJavaPerlException(ret) ; + } + + break ; + } + else{ + // Pass it on through the regular channel... + InlineJavaUtils.debug(3, "packet is not callback response: " + resp) ; + cmd = ijs.ProcessCommand(resp, false) ; + + continue ; + } + } + } + catch (IOException e){ + throw new InlineJavaException("IO error: " + e.getMessage()) ; + } + + response = ret ; + response_set = true ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaCallbackQueue.java b/Java/sources/org/perl/inline/java/InlineJavaCallbackQueue.java new file mode 100644 index 0000000..a1d7ab1 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaCallbackQueue.java @@ -0,0 +1,141 @@ +package org.perl.inline.java ; + +import java.util.* ; +import java.io.* ; + + +/* + Queue for callbacks to Perl... +*/ +class InlineJavaCallbackQueue { + // private InlineJavaServer ijs = InlineJavaServer.GetInstance() ; + private ArrayList queue = new ArrayList() ; + private boolean wait_interrupted = false ; + private boolean stream_opened = false ; + + + InlineJavaCallbackQueue(){ + } + + + synchronized void EnqueueCallback(InlineJavaCallback ijc){ + queue.add(ijc) ; + notify() ; + } + + + synchronized private InlineJavaCallback DequeueCallback(){ + if (GetSize() > 0){ + return (InlineJavaCallback)queue.remove(0) ; + } + return null ; + } + + + synchronized int WaitForCallback(double timeout){ + long secs = (long)Math.floor(timeout) ; + double rest = timeout - ((double)secs) ; + long millis = (long)Math.floor(rest * 1000.0) ; + rest = (rest * 1000.0) - ((double)millis) ; + int nanos = (int)Math.floor(rest * 1000000.0) ; + + return WaitForCallback((secs * 1000) + millis, nanos) ; + } + + + /* + Blocks up to the specified time for the next callback to arrive. + Returns -1 if the wait was interrupted voluntarily, 0 on timeout or + > 0 if a callback has arrived before the timeout expired. + */ + synchronized int WaitForCallback(long millis, int nanos){ + wait_interrupted = false ; + Thread t = Thread.currentThread() ; + InlineJavaUtils.debug(3, "waiting for callback request (" + millis + " millis, " + + nanos + " nanos) in " + t.getName() + "...") ; + + if (! stream_opened){ + return -1 ; + } + + while ((stream_opened)&&(! wait_interrupted)&&(IsEmpty())){ + try { + wait(millis, nanos) ; + // If we reach this code, it means the either we timed out + // or that we were notify()ed. + // In the former case, we must break out and return 0. + // In the latter case, either the queue will not be empty or + // wait_interrupted will be set. We must therefore also break out. + break ; + } + catch (InterruptedException ie){ + // Do nothing, return and wait() some more... + } + } + InlineJavaUtils.debug(3, "waiting for callback request finished " + t.getName() + "...") ; + + if (wait_interrupted){ + return -1 ; + } + else { + return GetSize() ; + } + } + + + /* + Waits indefinetely for the next callback to arrive and executes it. + Return true on success of false if the wait was interrupted voluntarily. + */ + synchronized boolean ProcessNextCallback() throws InlineJavaException, InlineJavaPerlException { + int rc = WaitForCallback(0, 0) ; + if (rc == -1){ + // Wait was interrupted + return false ; + } + + // DequeueCallback can't return null because we explicetely + // waited until a callback was there. + Thread t = Thread.currentThread() ; + InlineJavaUtils.debug(3, "processing callback request in " + t.getName() + "...") ; + InlineJavaCallback ijc = DequeueCallback() ; + ijc.Process() ; + ijc.NotifyOfResponse(t) ; + + return true ; + } + + + private boolean IsEmpty(){ + return (GetSize() == 0) ; + } + + + void OpenCallbackStream(){ + stream_opened = true ; + } + + + synchronized void CloseCallbackStream(){ + stream_opened = false ; + InterruptWaitForCallback() ; + } + + + boolean IsStreamOpen(){ + return stream_opened ; + } + + + int GetSize(){ + return queue.size() ; + } + + + synchronized private void InterruptWaitForCallback(){ + Thread t = Thread.currentThread() ; + InlineJavaUtils.debug(3, "interrupting wait for callback request in " + t.getName() + "...") ; + wait_interrupted = true ; + notify() ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaCastException.java b/Java/sources/org/perl/inline/java/InlineJavaCastException.java new file mode 100644 index 0000000..b799689 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaCastException.java @@ -0,0 +1,7 @@ +package org.perl.inline.java ; + +class InlineJavaCastException extends InlineJavaException { + InlineJavaCastException(String m){ + super(m) ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaClass.java b/Java/sources/org/perl/inline/java/InlineJavaClass.java new file mode 100644 index 0000000..bd57769 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaClass.java @@ -0,0 +1,554 @@ +package org.perl.inline.java ; + +import java.util.* ; +import java.lang.reflect.* ; + + +class InlineJavaClass { + private InlineJavaServer ijs ; + private InlineJavaProtocol ijp ; + + static private HashMap class2jni_code = new HashMap() ; + static { + class2jni_code.put(byte.class, "B") ; + class2jni_code.put(short.class, "S") ; + class2jni_code.put(int.class, "I") ; + class2jni_code.put(long.class, "J") ; + class2jni_code.put(float.class, "F") ; + class2jni_code.put(double.class, "D") ; + class2jni_code.put(boolean.class, "Z") ; + class2jni_code.put(char.class, "C") ; + class2jni_code.put(void.class, "V") ; + } ; + + static private HashMap class2wrapper = new HashMap() ; + static { + class2wrapper.put(byte.class, java.lang.Byte.class) ; + class2wrapper.put(short.class, java.lang.Short.class) ; + class2wrapper.put(int.class, java.lang.Integer.class) ; + class2wrapper.put(long.class, java.lang.Long.class) ; + class2wrapper.put(float.class, java.lang.Float.class) ; + class2wrapper.put(double.class, java.lang.Double.class) ; + class2wrapper.put(boolean.class, java.lang.Boolean.class) ; + class2wrapper.put(char.class, java.lang.Character.class) ; + class2wrapper.put(void.class, java.lang.Void.class) ; + } ; + + static private HashMap name2class = new HashMap() ; + static { + name2class.put("byte", byte.class) ; + name2class.put("short", short.class) ; + name2class.put("int", int.class) ; + name2class.put("long", long.class) ; + name2class.put("float", float.class) ; + name2class.put("double", double.class) ; + name2class.put("boolean", boolean.class) ; + name2class.put("char", char.class) ; + name2class.put("void", void.class) ; + name2class.put("B", byte.class) ; + name2class.put("S", short.class) ; + name2class.put("I", int.class) ; + name2class.put("J", long.class) ; + name2class.put("F", float.class) ; + name2class.put("D", double.class) ; + name2class.put("Z", boolean.class) ; + name2class.put("C", char.class) ; + name2class.put("V", void.class) ; + } ; + + + InlineJavaClass(InlineJavaServer _ijs, InlineJavaProtocol _ijp){ + ijs = _ijs ; + ijp = _ijp ; + } + + + /* + Makes sure a class exists + */ + static Class ValidateClass(String name) throws InlineJavaException { + Class pc = FindType(name) ; + if (pc != null){ + return pc ; + } + + try { + Class c = Class.forName(name, true, InlineJavaServer.GetInstance().GetUserClassLoader()) ; + return c ; + } + catch (ClassNotFoundException e){ + throw new InlineJavaException("Class " + name + " not found") ; + } + } + + + /* + Remove L...; from a class name if it has been extracted from an Array class name. + */ + static String CleanClassName(String name){ + if (name != null){ + int l = name.length() ; + if ((l > 2)&&(name.charAt(0) == 'L')&&(name.charAt(l - 1) == ';')){ + name = name.substring(1, l - 1) ; + } + } + return name ; + } + + + static private Class ValidateClassQuiet(String name){ + try { + return ValidateClass(name) ; + } + catch (InlineJavaException ije){ + return null ; + } + } + + /* + This is the monster method that determines how to cast arguments + */ + Object [] CastArguments(Class [] params, ArrayList args) throws InlineJavaException { + Object ret[] = new Object [params.length] ; + + for (int i = 0 ; i < params.length ; i++){ + // Here the args are all strings or objects (or undef) + // we need to match them to the prototype. + Class p = params[i] ; + InlineJavaUtils.debug(4, "arg " + String.valueOf(i) + " of signature is " + p.getName()) ; + + ret[i] = CastArgument(p, (String)args.get(i)) ; + } + + return ret ; + } + + + /* + This is the monster method that determines how to cast arguments + */ + Object CastArgument(Class p, String argument) throws InlineJavaException { + Object ret = null ; + + ArrayList tokens = new ArrayList() ; + StringTokenizer st = new StringTokenizer(argument, ":") ; + for (int j = 0 ; st.hasMoreTokens() ; j++){ + tokens.add(j, st.nextToken()) ; + } + if (tokens.size() == 1){ + tokens.add(1, "") ; + } + String type = (String)tokens.get(0) ; + + // We need to separate the primitive types from the + // reference types. + boolean num = ClassIsNumeric(p) ; + if ((num)||(ClassIsString(p))){ + Class ap = p ; + if (ap == java.lang.Number.class){ + InlineJavaUtils.debug(4, "specializing java.lang.Number to java.lang.Double") ; + ap = java.lang.Double.class ; + } + else if (ap.getName().equals("java.lang.CharSequence")){ + InlineJavaUtils.debug(4, "specializing java.lang.CharSequence to java.lang.String") ; + ap = java.lang.String.class ; + } + + if (type.equals("undef")){ + if (num){ + InlineJavaUtils.debug(4, "args is undef -> forcing to " + ap.getName() + " 0") ; + ret = ijp.CreateObject(ap, new Object [] {"0"}, new Class [] {String.class}) ; + InlineJavaUtils.debug(4, " result is " + ret.toString()) ; + } + else{ + ret = null ; + InlineJavaUtils.debug(4, "args is undef -> forcing to " + ap.getName() + " " + ret) ; + InlineJavaUtils.debug(4, " result is " + ret) ; + } + } + else if (type.equals("scalar")){ + String arg = ijp.Decode((String)tokens.get(1)) ; + InlineJavaUtils.debug(4, "args is scalar (" + arg + ") -> forcing to " + ap.getName()) ; + try { + ret = ijp.CreateObject(ap, new Object [] {arg}, new Class [] {String.class}) ; + InlineJavaUtils.debug(4, " result is " + ret.toString()) ; + } + catch (NumberFormatException e){ + throw new InlineJavaCastException("Can't convert " + arg + " to " + ap.getName()) ; + } + } + else if (type.equals("double")){ + String arg = ijp.Decode((String)tokens.get(1)) ; + // We have native double bytes in arg. + long l = 0 ; + char c[] = arg.toCharArray() ; + for (int i = 0 ; i < 8 ; i++){ + l += (((long)c[i]) << (8 * i)) ; + } + double d = Double.longBitsToDouble(l) ; + ret = new Double(d) ; + } + else { + throw new InlineJavaCastException("Can't convert reference to " + p.getName()) ; + } + } + else if (ClassIsBool(p)){ + if (type.equals("undef")){ + InlineJavaUtils.debug(4, "args is undef -> forcing to bool false") ; + ret = new Boolean("false") ; + InlineJavaUtils.debug(4, " result is " + ret.toString()) ; + } + else if (type.equals("scalar")){ + String arg = ijp.Decode((String)tokens.get(1)) ; + InlineJavaUtils.debug(4, "args is scalar (" + arg + ") -> forcing to bool") ; + if ((arg.equals(""))||(arg.equals("0"))){ + arg = "false" ; + } + else{ + arg = "true" ; + } + ret = new Boolean(arg) ; + InlineJavaUtils.debug(4, " result is " + ret.toString()) ; + } + else{ + throw new InlineJavaCastException("Can't convert reference to " + p.getName()) ; + } + } + else if (ClassIsChar(p)){ + if (type.equals("undef")){ + InlineJavaUtils.debug(4, "args is undef -> forcing to char '\0'") ; + ret = new Character('\0') ; + InlineJavaUtils.debug(4, " result is " + ret.toString()) ; + } + else if (type.equals("scalar")){ + String arg = ijp.Decode((String)tokens.get(1)) ; + InlineJavaUtils.debug(4, "args is scalar -> forcing to char") ; + char c = '\0' ; + if (arg.length() == 1){ + c = arg.toCharArray()[0] ; + } + else if (arg.length() > 1){ + throw new InlineJavaCastException("Can't convert " + arg + " to " + p.getName()) ; + } + ret = new Character(c) ; + InlineJavaUtils.debug(4, " result is " + ret.toString()) ; + } + else{ + throw new InlineJavaCastException("Can't convert reference to " + p.getName()) ; + } + } + else { + InlineJavaUtils.debug(4, "class " + p.getName() + " is reference") ; + // We know that what we expect here is a real object + if (type.equals("undef")){ + InlineJavaUtils.debug(4, "args is undef -> forcing to null") ; + ret = null ; + } + else if (type.equals("scalar")){ + // Here if we need a java.lang.Object.class, it's probably + // because we can store anything, so we use a String object. + if (p == java.lang.Object.class){ + String arg = ijp.Decode((String)tokens.get(1)) ; + ret = arg ; + } + else{ + throw new InlineJavaCastException("Can't convert primitive type to " + p.getName()) ; + } + } + else if (type.equals("java_object")){ + // We need an object and we got an object... + InlineJavaUtils.debug(4, "class " + p.getName() + " is reference") ; + + String c_name = (String)tokens.get(1) ; + String objid = (String)tokens.get(2) ; + + Class c = ValidateClass(c_name) ; + + if (DoesExtend(c, p) > -1){ + InlineJavaUtils.debug(4, " " + c.getName() + " is a kind of " + p.getName()) ; + // get the object from the hash table + int id = Integer.parseInt(objid) ; + Object o = ijs.GetObject(id) ; + ret = o ; + } + else{ + throw new InlineJavaCastException("Can't cast a " + c.getName() + " to a " + p.getName()) ; + } + } + else{ + InlineJavaUtils.debug(4, "class " + p.getName() + " is reference") ; + + String pkg = (String)tokens.get(1) ; + pkg = pkg.replace('/', ':') ; + String objid = (String)tokens.get(2) ; + + + if (DoesExtend(p, org.perl.inline.java.InlineJavaPerlObject.class) > -1){ + InlineJavaUtils.debug(4, " Perl object is a kind of " + p.getName()) ; + int id = Integer.parseInt(objid) ; + ret = new InlineJavaPerlObject(pkg, id) ; + } + else{ + throw new InlineJavaCastException("Can't cast a Perl object to a " + p.getName()) ; + } + } + } + + return ret ; + } + + + /* + Returns the number of levels that separate a from b + */ + static int DoesExtend(Class a, Class b){ + return DoesExtend(a, b, 0) ; + } + + + static int DoesExtend(Class a, Class b, int level){ + InlineJavaUtils.debug(4, "checking if " + a.getName() + " extends " + b.getName()) ; + + if (a == b){ + return level ; + } + + Class parent = a.getSuperclass() ; + if (parent != null){ + InlineJavaUtils.debug(4, " parent is " + parent.getName()) ; + int ret = DoesExtend(parent, b, level + 1) ; + if (ret != -1){ + return ret ; + } + } + + // Maybe b is an interface a implements it? + Class inter[] = a.getInterfaces() ; + for (int i = 0 ; i < inter.length ; i++){ + InlineJavaUtils.debug(4, " interface is " + inter[i].getName()) ; + int ret = DoesExtend(inter[i], b, level + 1) ; + if (ret != -1){ + return ret ; + } + } + + return -1 ; + } + + + /* + Finds the wrapper class for the passed primitive type. + */ + static Class FindWrapper(Class p){ + Class w = (Class)class2wrapper.get(p) ; + if (w == null){ + w = p ; + } + + return w ; + } + + + /* + Finds the primitive type class for the passed primitive type name. + */ + static Class FindType (String name){ + return (Class)name2class.get(name) ; + } + + + static String FindJNICode(Class p){ + if (! Object.class.isAssignableFrom(p)){ + return (String)class2jni_code.get(p) ; + } + else { + String name = p.getName().replace('.', '/') ; + if (p.isArray()){ + return name ; + } + else{ + return "L" + name + ";" ; + } + } + } + + + static boolean ClassIsPrimitive(Class p){ + String name = p.getName() ; + + if ((ClassIsNumeric(p))||(ClassIsString(p))||(ClassIsChar(p))||(ClassIsBool(p))){ + InlineJavaUtils.debug(4, "class " + name + " is primitive") ; + return true ; + } + + return false ; + } + + + /* + Determines if class is of numerical type. + */ + static private HashMap numeric_classes = new HashMap() ; + static { + Class [] list = { + java.lang.Byte.class, + java.lang.Short.class, + java.lang.Integer.class, + java.lang.Long.class, + java.lang.Float.class, + java.lang.Double.class, + java.lang.Number.class, + byte.class, + short.class, + int.class, + long.class, + float.class, + double.class, + } ; + for (int i = 0 ; i < list.length ; i++){ + numeric_classes.put(list[i], new Boolean(true)) ; + } + } + static boolean ClassIsNumeric (Class p){ + return (numeric_classes.get(p) != null) ; + } + + + static private HashMap double_classes = new HashMap() ; + static { + Class [] list = { + java.lang.Double.class, + double.class, + } ; + for (int i = 0 ; i < list.length ; i++){ + double_classes.put(list[i], new Boolean(true)) ; + } + } + static boolean ClassIsDouble (Class p){ + return (double_classes.get(p) != null) ; + } + + + /* + Class is String or StringBuffer + */ + static private HashMap string_classes = new HashMap() ; + static { + Class csq = ValidateClassQuiet("java.lang.CharSequence") ; + Class [] list = { + java.lang.String.class, + java.lang.StringBuffer.class, + csq + } ; + for (int i = 0 ; i < list.length ; i++){ + string_classes.put(list[i], new Boolean(true)) ; + } + } + static boolean ClassIsString (Class p){ + return (string_classes.get(p) != null) ; + } + + + /* + Class is Char + */ + static private HashMap char_classes = new HashMap() ; + static { + Class [] list = { + java.lang.Character.class, + char.class, + } ; + for (int i = 0 ; i < list.length ; i++){ + char_classes.put(list[i], new Boolean(true)) ; + } + } + static boolean ClassIsChar (Class p){ + return (char_classes.get(p) != null) ; + } + + + /* + Class is Bool + */ + static private HashMap bool_classes = new HashMap() ; + static { + Class [] list = { + java.lang.Boolean.class, + boolean.class, + } ; + for (int i = 0 ; i < list.length ; i++){ + bool_classes.put(list[i], new Boolean(true)) ; + } + } + static boolean ClassIsBool (Class p){ + return (bool_classes.get(p) != null) ; + } + + + /* + Determines if a class is not of a primitive type or of a + wrapper class. + */ + static boolean ClassIsReference (Class p){ + String name = p.getName() ; + + if (ClassIsPrimitive(p)){ + return false ; + } + + InlineJavaUtils.debug(4, "class " + name + " is reference") ; + + return true ; + } + + + static boolean ClassIsArray (Class p){ + String name = p.getName() ; + + if ((ClassIsReference(p))&&(name.startsWith("["))){ + InlineJavaUtils.debug(4, "class " + name + " is array") ; + return true ; + } + + return false ; + } + + + static boolean ClassIsPublic (Class p){ + int pub = p.getModifiers() & Modifier.PUBLIC ; + if (pub != 0){ + return true ; + } + + return false ; + } + + + static boolean ClassIsHandle (Class p){ + if ((ClassIsReadHandle(p))||(ClassIsWriteHandle(p))){ + return true ; + } + + return false ; + } + + + static boolean ClassIsReadHandle (Class p){ + if ((java.io.Reader.class.isAssignableFrom(p))|| + (java.io.InputStream.class.isAssignableFrom(p))){ + return true ; + } + + return false ; + } + + + static boolean ClassIsWriteHandle (Class p){ + if ((java.io.Writer.class.isAssignableFrom(p))|| + (java.io.OutputStream.class.isAssignableFrom(p))){ + return true ; + } + + return false ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaException.java b/Java/sources/org/perl/inline/java/InlineJavaException.java new file mode 100644 index 0000000..7e2f3cd --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaException.java @@ -0,0 +1,7 @@ +package org.perl.inline.java ; + +public class InlineJavaException extends Exception { + public InlineJavaException(String s) { + super(s) ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaHandle.java b/Java/sources/org/perl/inline/java/InlineJavaHandle.java new file mode 100644 index 0000000..681cf18 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaHandle.java @@ -0,0 +1,130 @@ +package org.perl.inline.java ; + + +import java.util.* ; +import java.io.* ; + + +public class InlineJavaHandle { + private static final String charset = "ISO-8859-1" ; + + + static String read(Object o, int len) throws InlineJavaException, IOException { + String ret = null ; + if (InlineJavaClass.ClassIsReadHandle(o.getClass())){ + if (o instanceof java.io.Reader){ + char buf[] = new char[len] ; + int rc = ((java.io.Reader)o).read(buf) ; + if (rc != -1){ + ret = new String(buf) ; + } + } + else { + byte buf[] = new byte[len] ; + int rc = ((java.io.InputStream)o).read(buf) ; + if (rc != -1){ + ret = new String(buf, charset) ; + } + } + } + else { + throw new InlineJavaException("Can't read from non-readhandle object (" + o.getClass().getName() + ")") ; + } + + return ret ; + } + + + static String readLine(Object o) throws InlineJavaException, IOException { + String ret = null ; + if (InlineJavaClass.ClassIsReadHandle(o.getClass())){ + if (o instanceof java.io.BufferedReader){ + ret = ((java.io.BufferedReader)o).readLine() ; + } + else { + throw new InlineJavaException("Can't read line from non-buffered Reader or InputStream") ; + } + } + else { + throw new InlineJavaException("Can't read line from non-readhandle object (" + o.getClass().getName() + ")") ; + } + + return ret ; + } + + + static Object makeBuffered(Object o) throws InlineJavaException, IOException { + Object ret = null ; + if (InlineJavaClass.ClassIsReadHandle(o.getClass())){ + if (o instanceof java.io.BufferedReader){ + ret = (java.io.BufferedReader)o ; + } + else if (o instanceof java.io.Reader){ + ret = new BufferedReader((java.io.Reader)o) ; + } + else { + ret = new BufferedReader(new InputStreamReader((java.io.InputStream)o, charset)) ; + } + } + else if (InlineJavaClass.ClassIsWriteHandle(o.getClass())){ + if (o instanceof java.io.BufferedWriter){ + ret = (java.io.BufferedWriter)o ; + } + else if (o instanceof java.io.Writer){ + ret = new BufferedWriter((java.io.Writer)o) ; + } + else { + ret = new BufferedWriter(new OutputStreamWriter((java.io.OutputStream)o, charset)) ; + } + } + else { + throw new InlineJavaException("Can't make non-handle object buffered (" + o.getClass().getName() + ")") ; + } + + return ret ; + } + + + static int write(Object o, String str) throws InlineJavaException, IOException { + int ret = -1 ; + if (InlineJavaClass.ClassIsWriteHandle(o.getClass())){ + if (o instanceof java.io.Writer){ + ((java.io.Writer)o).write(str) ; + ret = str.length() ; + } + else { + byte b[] = str.getBytes(charset) ; + ((java.io.OutputStream)o).write(b) ; + ret = b.length ; + } + } + else { + throw new InlineJavaException("Can't write to non-writehandle object (" + o.getClass().getName() + ")") ; + } + + return ret ; + } + + + static void close(Object o) throws InlineJavaException, IOException { + if (InlineJavaClass.ClassIsReadHandle(o.getClass())){ + if (o instanceof java.io.Reader){ + ((java.io.Reader)o).close() ; + } + else { + ((java.io.InputStream)o).close() ; + } + } + else if (InlineJavaClass.ClassIsWriteHandle(o.getClass())){ + if (o instanceof java.io.Writer){ + ((java.io.Writer)o).close() ; + } + else { + ((java.io.OutputStream)o).close() ; + } + } + else { + throw new InlineJavaException("Can't close non-handle object (" + o.getClass().getName() + ")") ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaInvocationTargetException.java b/Java/sources/org/perl/inline/java/InlineJavaInvocationTargetException.java new file mode 100644 index 0000000..768bb1a --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaInvocationTargetException.java @@ -0,0 +1,15 @@ +package org.perl.inline.java ; + +class InlineJavaInvocationTargetException extends InlineJavaException { + private Throwable t ; + + + InlineJavaInvocationTargetException(String m, Throwable _t){ + super(m) ; + t = _t ; + } + + Throwable GetThrowable(){ + return t ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaPerlCaller.java b/Java/sources/org/perl/inline/java/InlineJavaPerlCaller.java new file mode 100644 index 0000000..c389358 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaPerlCaller.java @@ -0,0 +1,257 @@ +package org.perl.inline.java ; + +import java.util.* ; +import java.io.* ; + + +/* + Callback to Perl... +*/ +public class InlineJavaPerlCaller { + private InlineJavaServer ijs = InlineJavaServer.GetInstance() ; + private Thread creator = null ; + static private Map thread_callback_queues = Collections.synchronizedMap(new HashMap()) ; + static private ResourceBundle resources = null ; + static private boolean inited = false ; + + + /* + Only thread that communicate with Perl are allowed to create PerlCallers because + this is where we get the thread that needs to be notified when the callbacks come in. + */ + public InlineJavaPerlCaller() throws InlineJavaException { + init() ; + Thread t = Thread.currentThread() ; + if (ijs.IsThreadPerlContact(t)){ + creator = t ; + } + else{ + throw new InlineJavaException("InlineJavaPerlCaller objects can only be created by threads that communicate directly with Perl") ; + } + } + + + synchronized static protected void init() throws InlineJavaException { + if (! inited){ + try { + resources = ResourceBundle.getBundle("InlineJava") ; + + inited = true ; + } + catch (MissingResourceException mre){ + throw new InlineJavaException("Error loading InlineJava.properties: " + mre.getMessage()) ; + } + } + } + + + static protected ResourceBundle GetBundle(){ + return resources ; + } + + /* Old interface */ + /** + * @deprecated As of 0.48, replaced by {@link #CallPerlSub(String,Object[])} + */ + public Object CallPerl(String pkg, String method, Object args[]) throws InlineJavaException, InlineJavaPerlException { + return CallPerl(pkg, method, args, null) ; + } + + + /* Old interface */ + /** + * @deprecated As of 0.48, replaced by {@link #CallPerlSub(String,Object[],Class)} + */ + public Object CallPerl(String pkg, String method, Object args[], String cast) throws InlineJavaException, InlineJavaPerlException { + InlineJavaCallback ijc = new InlineJavaCallback( + (String)null, pkg + "::" + method, args, + (cast == null ? null : InlineJavaClass.ValidateClass(cast))) ; + return CallPerl(ijc) ; + } + + + /* New interface */ + public Object CallPerlSub(String sub, Object args[]) throws InlineJavaException, InlineJavaPerlException { + return CallPerlSub(sub, args, null) ; + } + + + /* New interface */ + public Object CallPerlSub(String sub, Object args[], Class cast) throws InlineJavaException, InlineJavaPerlException { + InlineJavaCallback ijc = new InlineJavaCallback( + (String)null, sub, args, cast) ; + return CallPerl(ijc) ; + } + + + /* New interface */ + public Object CallPerlMethod(InlineJavaPerlObject obj, String method, Object args[]) throws InlineJavaException, InlineJavaPerlException { + return CallPerlMethod(obj, method, args, null) ; + } + + + /* New interface */ + public Object CallPerlMethod(InlineJavaPerlObject obj, String method, Object args[], Class cast) throws InlineJavaException, InlineJavaPerlException { + InlineJavaCallback ijc = new InlineJavaCallback( + obj, method, args, cast) ; + return CallPerl(ijc) ; + } + + + /* New interface */ + public Object CallPerlStaticMethod(String pkg, String method, Object args[]) throws InlineJavaException, InlineJavaPerlException { + return CallPerlStaticMethod(pkg, method, args, null) ; + } + + + /* New interface */ + public Object CallPerlStaticMethod(String pkg, String method, Object args[], Class cast) throws InlineJavaException, InlineJavaPerlException { + InlineJavaCallback ijc = new InlineJavaCallback( + pkg, method, args, cast) ; + return CallPerl(ijc) ; + } + + + public Object eval(String code) throws InlineJavaPerlException, InlineJavaException { + return eval(code, null) ; + } + + + public Object eval(String code, Class cast) throws InlineJavaPerlException, InlineJavaException { + return CallPerlSub("Inline::Java::Callback::java_eval", new Object [] {code}, cast) ; + } + + + public Object require(String module_or_file) throws InlineJavaPerlException, InlineJavaException { + return CallPerlSub("Inline::Java::Callback::java_require", new Object [] {module_or_file}) ; + } + + + public Object require_file(String file) throws InlineJavaPerlException, InlineJavaException { + return CallPerlSub("Inline::Java::Callback::java_require", new Object [] {file, new Boolean("true")}) ; + } + + + public Object require_module(String module) throws InlineJavaPerlException, InlineJavaException { + return CallPerlSub("Inline::Java::Callback::java_require", new Object [] {module, new Boolean("false")}) ; + } + + + private Object CallPerl(InlineJavaCallback ijc) throws InlineJavaException, InlineJavaPerlException { + Thread t = Thread.currentThread() ; + if (t == creator){ + ijc.Process() ; + return ijc.GetResponse() ; + } + else { + // Enqueue the callback into the creator thread's queue and notify it + // that there is some work for him. + ijc.ClearResponse() ; + InlineJavaCallbackQueue q = GetQueue(creator) ; + InlineJavaUtils.debug(3, "enqueing callback for processing for " + creator.getName() + " in " + t.getName() + "...") ; + q.EnqueueCallback(ijc) ; + InlineJavaUtils.debug(3, "notifying that a callback request is available for " + creator.getName() + " in " + t.getName()) ; + + // Now we must wait until the callback is processed and get back the result... + return ijc.WaitForResponse(t) ; + } + } + + + public void OpenCallbackStream() throws InlineJavaException { + Thread t = Thread.currentThread() ; + if (! ijs.IsThreadPerlContact(t)){ + throw new InlineJavaException("InlineJavaPerlCaller.OpenCallbackStream() can only be called by threads that communicate directly with Perl") ; + } + + InlineJavaCallbackQueue q = GetQueue(t) ; + q.OpenCallbackStream() ; + } + + + /* + Blocks until either a callback arrives, timeout seconds has passed or the call is + interrupted by Interrupt? + */ + public int WaitForCallback(double timeout) throws InlineJavaException { + Thread t = Thread.currentThread() ; + if (! ijs.IsThreadPerlContact(t)){ + throw new InlineJavaException("InlineJavaPerlCaller.WaitForCallback() can only be called by threads that communicate directly with Perl") ; + } + + InlineJavaCallbackQueue q = GetQueue(t) ; + if (timeout == 0.0){ + // no wait + return q.GetSize() ; + } + else if (timeout == -1.0){ + timeout = 0.0 ; + } + + return q.WaitForCallback(timeout) ; + } + + + public boolean ProcessNextCallback() throws InlineJavaException, InlineJavaPerlException { + Thread t = Thread.currentThread() ; + if (! ijs.IsThreadPerlContact(t)){ + throw new InlineJavaException("InlineJavaPerlCaller.ProcessNextCallback() can only be called by threads that communicate directly with Perl") ; + } + + InlineJavaCallbackQueue q = GetQueue(t) ; + return q.ProcessNextCallback() ; + } + + + public void CloseCallbackStream() throws InlineJavaException { + InlineJavaCallbackQueue q = GetQueue(creator) ; + q.CloseCallbackStream() ; + } + + + public void StartCallbackLoop() throws InlineJavaException, InlineJavaPerlException { + Thread t = Thread.currentThread() ; + if (! ijs.IsThreadPerlContact(t)){ + throw new InlineJavaException("InlineJavaPerlCaller.StartCallbackLoop() can only be called by threads that communicate directly with Perl") ; + } + + InlineJavaCallbackQueue q = GetQueue(t) ; + InlineJavaUtils.debug(3, "starting callback loop for " + creator.getName() + " in " + t.getName()) ; + q.OpenCallbackStream() ; + while (q.IsStreamOpen()){ + q.ProcessNextCallback() ; + } + } + + + public void StopCallbackLoop() throws InlineJavaException { + Thread t = Thread.currentThread() ; + InlineJavaCallbackQueue q = GetQueue(creator) ; + InlineJavaUtils.debug(3, "stopping callback loop for " + creator.getName() + " in " + t.getName()) ; + q.CloseCallbackStream() ; + } + + + /* + Here the prototype accepts Threads because the JNI thread + calls this method also. + */ + static synchronized void AddThread(Thread t){ + thread_callback_queues.put(t, new InlineJavaCallbackQueue()) ; + } + + + static synchronized void RemoveThread(InlineJavaServerThread t){ + thread_callback_queues.remove(t) ; + } + + + static private InlineJavaCallbackQueue GetQueue(Thread t) throws InlineJavaException { + InlineJavaCallbackQueue q = (InlineJavaCallbackQueue)thread_callback_queues.get(t) ; + + if (q == null){ + throw new InlineJavaException("Can't find thread " + t.getName() + "!") ; + } + return q ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaPerlException.java b/Java/sources/org/perl/inline/java/InlineJavaPerlException.java new file mode 100644 index 0000000..38fd631 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaPerlException.java @@ -0,0 +1,25 @@ +package org.perl.inline.java ; + + +public class InlineJavaPerlException extends Exception { + private Object obj ; + + + public InlineJavaPerlException(Object o){ + super(o.toString()) ; + obj = o ; + } + + public InlineJavaPerlException(String s){ + super(s) ; + obj = s ; + } + + public Object GetObject(){ + return obj ; + } + + public String GetString(){ + return (String)obj ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaPerlInterpreter.java b/Java/sources/org/perl/inline/java/InlineJavaPerlInterpreter.java new file mode 100644 index 0000000..6a24ce2 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaPerlInterpreter.java @@ -0,0 +1,107 @@ +package org.perl.inline.java ; + + +import java.util.* ; +import java.io.* ; + + +/* + InlineJavaPerlInterpreter + + This singleton class creates a PerlInterpreter object. To this object is bound + an instance of InlineJavaServer that will allow communication with Perl. + + All communication with Perl must be done via InlineJavaPerlCaller in order to insure + thread synchronization. Therefore all Perl actions will be implemented via functions + in Inline::Java::PerlInterperter so that they can be called via InlineJavaPerlCaller +*/ +public class InlineJavaPerlInterpreter extends InlineJavaPerlCaller { + static private boolean inited = false ; + static InlineJavaPerlInterpreter instance = null ; + static boolean test = false ; + static String libperl_so = "" ; + + + protected InlineJavaPerlInterpreter() throws InlineJavaPerlException, InlineJavaException { + init() ; + + InlineJavaUtils.debug(2, "constructing perl interpreter") ; + construct() ; + InlineJavaUtils.debug(2, "perl interpreter constructed") ; + + if (! libperl_so.equals("")){ + evalNoReturn("require DynaLoader ;") ; + evalNoReturn("DynaLoader::dl_load_file(\"" + libperl_so + "\", 0x01) ;") ; + } + if (test){ + evalNoReturn("use blib ;") ; + } + evalNoReturn("use Inline::Java::PerlInterpreter ;") ; + } + + + synchronized static public InlineJavaPerlInterpreter create() throws InlineJavaPerlException, InlineJavaException { + if (instance == null){ + // Here we create a temporary InlineJavaServer instance in order to be able to instanciate + // ourselves. When we create InlineJavaPerlInterpreter, the instance will be overriden. + InlineJavaUtils.debug(2, "creating temporary JNI InlineJavaServer") ; + InlineJavaServer.jni_main(InlineJavaUtils.get_debug(), false) ; + InlineJavaUtils.debug(2, "temporary JNI InlineJavaServer created") ; + InlineJavaUtils.debug(2, "creating InlineJavaPerlInterpreter") ; + instance = new InlineJavaPerlInterpreter() ; + InlineJavaUtils.debug(2, "InlineJavaPerlInterpreter created") ; + } + return instance ; + } + + + synchronized static protected void init() throws InlineJavaException { + init("install") ; + } + + + synchronized static protected void init(String mode) throws InlineJavaException { + InlineJavaPerlCaller.init() ; + if (! inited){ + test = (mode.equals("test") ? true : false) ; + try { + String perlinterpreter_so = GetBundle().getString("inline_java_perlinterpreter_so_" + mode) ; + File f = new File(perlinterpreter_so) ; + if (! f.exists()){ + throw new InlineJavaException("Can't initialize PerlInterpreter " + + "functionnality: PerlInterpreter extension (" + perlinterpreter_so + + ") can't be found") ; + } + + // Load the PerlInterpreter shared object + InlineJavaUtils.debug(2, "loading shared library " + perlinterpreter_so) ; + System.load(perlinterpreter_so) ; + InlineJavaUtils.debug(2, "shared library " + perlinterpreter_so + " loaded") ; + + libperl_so = GetBundle().getString("inline_java_libperl_so") ; + + inited = true ; + } + catch (MissingResourceException mre){ + throw new InlineJavaException("Error loading InlineJava.properties resource: " + mre.getMessage()) ; + } + } + } + + + synchronized static private native void construct() ; + + + synchronized static private native void evalNoReturn(String code) throws InlineJavaPerlException ; + + + synchronized static private native void destruct() ; + + + synchronized static public void destroy() { + if (instance != null){ + destruct() ; + instance = null ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaPerlNatives.java b/Java/sources/org/perl/inline/java/InlineJavaPerlNatives.java new file mode 100644 index 0000000..5682e6a --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaPerlNatives.java @@ -0,0 +1,231 @@ +package org.perl.inline.java ; + +import java.lang.reflect.* ; +import java.util.* ; +import java.io.* ; + + +public class InlineJavaPerlNatives extends InlineJavaPerlCaller { + static private boolean inited = false ; + static private Map registered_classes = Collections.synchronizedMap(new HashMap()) ; + static private Map registered_methods = Collections.synchronizedMap(new HashMap()) ; + + + protected InlineJavaPerlNatives() throws InlineJavaException { + init() ; + RegisterPerlNatives(this.getClass()) ; + } + + + static protected void init() throws InlineJavaException { + init("install") ; + } + + + synchronized static protected void init(String mode) throws InlineJavaException { + InlineJavaPerlCaller.init() ; + if (! inited){ + try { + String perlnatives_so = GetBundle().getString("inline_java_perlnatives_so_" + mode) ; + File f = new File(perlnatives_so) ; + if (! f.exists()){ + throw new InlineJavaException("Can't initialize PerlNatives " + + "functionnality: PerlNatives extension (" + perlnatives_so + + ") can't be found") ; + } + + try { + Class ste_class = Class.forName("java.lang.StackTraceElement") ; + } + catch (ClassNotFoundException cnfe){ + throw new InlineJavaException("Can't initialize PerlNatives " + + "functionnality: Java 1.4 or higher required (current is " + + System.getProperty("java.version") + ").") ; + } + + // Load the Natives shared object + InlineJavaUtils.debug(2, "loading shared library " + perlnatives_so) ; + System.load(perlnatives_so) ; + + inited = true ; + } + catch (MissingResourceException mre){ + throw new InlineJavaException("Error loading InlineJava.properties resource: " + mre.getMessage()) ; + } + } + } + + + // This method actually does the real work of registering the methods. + synchronized private void RegisterPerlNatives(Class c) throws InlineJavaException { + if (registered_classes.get(c) == null){ + InlineJavaUtils.debug(3, "registering natives for class " + c.getName()) ; + + Constructor constructors[] = c.getDeclaredConstructors() ; + Method methods[] = c.getDeclaredMethods() ; + + registered_classes.put(c, c) ; + for (int i = 0 ; i < constructors.length ; i++){ + Constructor x = constructors[i] ; + if (Modifier.isNative(x.getModifiers())){ + RegisterMethod(c, "new", x.getParameterTypes(), c) ; + } + } + + for (int i = 0 ; i < methods.length ; i++){ + Method x = methods[i] ; + if (Modifier.isNative(x.getModifiers())){ + RegisterMethod(c, x.getName(), x.getParameterTypes(), x.getReturnType()) ; + } + } + } + } + + + private void RegisterMethod(Class c, String mname, Class params[], Class rt) throws InlineJavaException { + String cname = c.getName() ; + InlineJavaUtils.debug(3, "registering native method " + mname + " for class " + cname) ; + + // Check return type + if ((! Object.class.isAssignableFrom(rt))&&(rt != void.class)){ + throw new InlineJavaException("Perl native method " + mname + " of class " + cname + " can only have Object or void return types (not " + rt.getName() + ")") ; + } + + // fmt starts with the return type, which for now is Object only (or void). + StringBuffer fmt = new StringBuffer("L") ; + StringBuffer sign = new StringBuffer("(") ; + for (int i = 0 ; i < params.length ; i++){ + String code = InlineJavaClass.FindJNICode(params[i]) ; + sign.append(code) ; + char ch = code.charAt(0) ; + char f = ch ; + if (f == '['){ + // Arrays are Objects... + f = 'L' ; + } + fmt.append(new String(new char [] {f})) ; + } + sign.append(")") ; + + sign.append(InlineJavaClass.FindJNICode(rt)) ; + InlineJavaUtils.debug(3, "signature is " + sign) ; + InlineJavaUtils.debug(3, "format is " + fmt) ; + + // For now, no method overloading so no signature necessary + String meth = cname + "." + mname ; + String prev = (String)registered_methods.get(meth) ; + if (prev != null){ + throw new InlineJavaException("There already is a native method '" + mname + "' registered for class '" + cname + "'") ; + } + registered_methods.put(meth, fmt.toString()) ; + + // call the native method to hook it up + RegisterMethod(c, mname, sign.toString()) ; + } + + + // This native method will call RegisterNative to hook up the magic + // method implementation for the method. + native private void RegisterMethod(Class c, String name, String signature) throws InlineJavaException ; + + + // This method will be called from the native side. We need to figure + // out who this method is and then look in up in the + // registered method list and return the format. + private String LookupMethod() throws InlineJavaException { + InlineJavaUtils.debug(3, "entering LookupMethod") ; + + String caller[] = GetNativeCaller() ; + String meth = caller[0] + "." + caller[1] ; + + String fmt = (String)registered_methods.get(meth) ; + if (fmt == null){ + throw new InlineJavaException("Native method " + meth + " is not registered") ; + } + + InlineJavaUtils.debug(3, "exiting LookupMethod") ; + + return fmt ; + } + + + private Object InvokePerlMethod(Object args[]) throws InlineJavaException, InlineJavaPerlException { + InlineJavaUtils.debug(3, "entering InvokePerlMethod") ; + + String caller[] = GetNativeCaller() ; + String pkg = caller[0] ; + String method = caller[1] ; + + // Transform the Java class name into the Perl package name + StringTokenizer st = new StringTokenizer(pkg, ".") ; + StringBuffer perl_sub = new StringBuffer() ; + // Starting with "::" means that the package is relative to the caller package + while (st.hasMoreTokens()){ + perl_sub.append("::" + st.nextToken()) ; + } + perl_sub.append("::" + method) ; + + for (int i = 0 ; i < args.length ; i++){ + InlineJavaUtils.debug(3, "InvokePerlMethod argument " + i + " = " + args[i]) ; + } + + Object ret = CallPerlSub(perl_sub.toString(), args) ; + + InlineJavaUtils.debug(3, "exiting InvokePerlMethod") ; + + return ret ; + } + + + // This method must absolutely be called by a method DIRECTLY called + // by generic_perl_native + private String[] GetNativeCaller() throws InlineJavaException { + InlineJavaUtils.debug(3, "entering GetNativeCaller") ; + + Class ste_class = null ; + try { + ste_class = Class.forName("java.lang.StackTraceElement") ; + } + catch (ClassNotFoundException cnfe){ + throw new InlineJavaException("Can't load class java.lang.StackTraceElement") ; + } + + Throwable exec_point = new Throwable() ; + try { + Method m = exec_point.getClass().getMethod("getStackTrace", new Class [] {}) ; + Object stack = m.invoke(exec_point, new Object [] {}) ; + if (Array.getLength(stack) <= 2){ + throw new InlineJavaException("Improper use of InlineJavaPerlNatives.GetNativeCaller (call stack too short)") ; + } + + Object ste = Array.get(stack, 2) ; + m = ste.getClass().getMethod("isNativeMethod", new Class [] {}) ; + Boolean is_nm = (Boolean)m.invoke(ste, new Object [] {}) ; + if (! is_nm.booleanValue()){ + throw new InlineJavaException("Improper use of InlineJavaPerlNatives.GetNativeCaller (caller is not native)") ; + } + + m = ste.getClass().getMethod("getClassName", new Class [] {}) ; + String cname = (String)m.invoke(ste, new Object [] {}) ; + m = ste.getClass().getMethod("getMethodName", new Class [] {}) ; + String mname = (String)m.invoke(ste, new Object [] {}) ; + + InlineJavaUtils.debug(3, "exiting GetNativeCaller") ; + + return new String [] {cname, mname} ; + } + catch (NoSuchMethodException nsme){ + throw new InlineJavaException("Error manipulating java.lang.StackTraceElement classes: " + + nsme.getMessage()) ; + } + catch (IllegalAccessException iae){ + throw new InlineJavaException("Error manipulating java.lang.StackTraceElement classes: " + + iae.getMessage()) ; + } + catch (InvocationTargetException ite){ + // None of the methods invoked throw exceptions, so... + throw new InlineJavaException("Exception caught while manipulating java.lang.StackTraceElement classes: " + + ite.getTargetException()) ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaPerlObject.java b/Java/sources/org/perl/inline/java/InlineJavaPerlObject.java new file mode 100644 index 0000000..b57caab --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaPerlObject.java @@ -0,0 +1,73 @@ +package org.perl.inline.java ; + + +/* + InlineJavaPerlObject +*/ +public class InlineJavaPerlObject extends InlineJavaPerlCaller { + private int id = 0 ; + private String pkg = null ; + + + /* + Creates a Perl Object by calling + pkg->new(args) ; + */ + public InlineJavaPerlObject(String _pkg, Object args[]) throws InlineJavaPerlException, InlineJavaException { + pkg = _pkg ; + InlineJavaPerlObject stub = (InlineJavaPerlObject)CallPerlStaticMethod(pkg, "new", args, getClass()) ; + id = stub.GetId() ; + stub.id = 0 ; + } + + + /* + This is just a stub for already existing objects + */ + InlineJavaPerlObject(String _pkg, int _id) throws InlineJavaException { + pkg = _pkg ; + id = _id ; + } + + + int GetId(){ + return id ; + } + + + public String GetPkg(){ + return pkg ; + } + + + public Object InvokeMethod(String name, Object args[]) throws InlineJavaPerlException, InlineJavaException { + return InvokeMethod(name, args, null) ; + } + + + public Object InvokeMethod(String name, Object args[], Class cast) throws InlineJavaPerlException, InlineJavaException { + return CallPerlMethod(this, name, args, cast) ; + } + + + public void Dispose() throws InlineJavaPerlException, InlineJavaException { + Dispose(false) ; + } + + + protected void Dispose(boolean gc) throws InlineJavaPerlException, InlineJavaException { + if (id != 0){ + CallPerlSub("Inline::Java::Callback::java_finalize", new Object [] {new Integer(id), new Boolean(gc)}) ; + } + } + + + protected void finalize() throws Throwable { + try { + Dispose(true) ; + } + finally { + super.finalize() ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaProtocol.java b/Java/sources/org/perl/inline/java/InlineJavaProtocol.java new file mode 100644 index 0000000..e84c214 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaProtocol.java @@ -0,0 +1,879 @@ +package org.perl.inline.java ; + +import java.util.* ; +import java.io.* ; +import java.lang.reflect.* ; + + +/* + This is where most of the work of Inline Java is done. Here determine + the request type and then we proceed to serve it. +*/ +class InlineJavaProtocol { + private InlineJavaServer ijs ; + private InlineJavaClass ijc ; + private InlineJavaArray ija ; + private String cmd ; + private String response = null ; + + private final String encoding = "UTF-8" ; + + static private Map member_cache = Collections.synchronizedMap(new HashMap()) ; + static private final String report_version = "V2" ; + + InlineJavaProtocol(InlineJavaServer _ijs, String _cmd) { + ijs = _ijs ; + ijc = new InlineJavaClass(ijs, this) ; + ija = new InlineJavaArray(ijc) ; + + cmd = _cmd ; + } + + + /* + Starts the analysis of the command line + */ + void Do() throws InlineJavaException { + StringTokenizer st = new StringTokenizer(cmd, " ") ; + String c = st.nextToken() ; + + if (c.equals("call_method")){ + CallJavaMethod(st) ; + } + else if (c.equals("set_member")){ + SetJavaMember(st) ; + } + else if (c.equals("get_member")){ + GetJavaMember(st) ; + } + else if (c.equals("add_classpath")){ + AddClassPath(st) ; + } + else if (c.equals("server_type")){ + ServerType(st) ; + } + else if (c.equals("report")){ + Report(st) ; + } + else if (c.equals("isa")){ + IsA(st) ; + } + else if (c.equals("create_object")){ + CreateJavaObject(st) ; + } + else if (c.equals("delete_object")){ + DeleteJavaObject(st) ; + } + else if (c.equals("obj_cnt")){ + ObjectCount(st) ; + } + else if (c.equals("cast")){ + Cast(st) ; + } + else if (c.equals("read")){ + Read(st) ; + } + else if (c.equals("make_buffered")){ + MakeBuffered(st) ; + } + else if (c.equals("readline")){ + ReadLine(st) ; + } + else if (c.equals("write")){ + Write(st) ; + } + else if (c.equals("close")){ + Close(st) ; + } + else if (c.equals("die")){ + InlineJavaUtils.debug(1, "received a request to die...") ; + ijs.Shutdown() ; + } + else { + throw new InlineJavaException("Unknown command " + c) ; + } + } + + /* + Returns a report on the Java classes, listing all public methods + and members + */ + void Report(StringTokenizer st) throws InlineJavaException { + StringBuffer pw = new StringBuffer(report_version + "\n") ; + + StringTokenizer st2 = new StringTokenizer(st.nextToken(), ":") ; + st2.nextToken() ; + + StringTokenizer st3 = new StringTokenizer(Decode(st2.nextToken()), " ") ; + + ArrayList class_list = new ArrayList() ; + while (st3.hasMoreTokens()){ + String c = st3.nextToken() ; + class_list.add(class_list.size(), c) ; + } + + for (int i = 0 ; i < class_list.size() ; i++){ + String name = (String)class_list.get(i) ; + Class c = ijc.ValidateClass(name) ; + + InlineJavaUtils.debug(3, "reporting for " + c) ; + + Class parent = c.getSuperclass() ; + String pname = (parent == null ? "null" : parent.getName()) ; + pw.append("class " + c.getName() + " " + pname + "\n") ; + Constructor constructors[] = c.getConstructors() ; + Method methods[] = c.getMethods() ; + Field fields[] = c.getFields() ; + + boolean pub = ijc.ClassIsPublic(c) ; + if (pub){ + // If the class is public and has no constructors, + // we provide a default no-arg constructors. + if (c.getDeclaredConstructors().length == 0){ + String noarg_sign = InlineJavaUtils.CreateSignature(new Class [] {}) ; + pw.append("constructor " + noarg_sign + "\n") ; + } + } + + boolean pn = InlineJavaPerlNatives.class.isAssignableFrom(c) ; + for (int j = 0 ; j < constructors.length ; j++){ + Constructor x = constructors[j] ; + if ((pn)&&(Modifier.isNative(x.getModifiers()))){ + continue ; + } + Class params[] = x.getParameterTypes() ; + String sign = InlineJavaUtils.CreateSignature(params) ; + Class decl = x.getDeclaringClass() ; + pw.append("constructor " + sign + "\n") ; + } + + for (int j = 0 ; j < methods.length ; j++){ + Method x = methods[j] ; + if ((pn)&&(Modifier.isNative(x.getModifiers()))){ + continue ; + } + String stat = (Modifier.isStatic(x.getModifiers()) ? " static " : " instance ") ; + String sign = InlineJavaUtils.CreateSignature(x.getParameterTypes()) ; + Class decl = x.getDeclaringClass() ; + pw.append("method" + stat + decl.getName() + " " + x.getName() + sign + "\n") ; + } + + for (int j = 0 ; j < fields.length ; j++){ + Field x = fields[(InlineJavaUtils.ReverseMembers() ? (fields.length - 1 - j) : j)] ; + String stat = (Modifier.isStatic(x.getModifiers()) ? " static " : " instance ") ; + Class decl = x.getDeclaringClass() ; + Class type = x.getType() ; + pw.append("field" + stat + decl.getName() + " " + x.getName() + " " + type.getName() + "\n") ; + } + } + + SetResponse(pw.toString()) ; + } + + + void AddClassPath(StringTokenizer st) throws InlineJavaException { + while (st.hasMoreTokens()){ + String path = Decode(st.nextToken()) ; + InlineJavaServer.GetInstance().GetUserClassLoader().AddClassPath(path) ; + } + SetResponse(null) ; + } + + + void ServerType(StringTokenizer st) throws InlineJavaException { + SetResponse(ijs.GetType()) ; + } + + + void IsA(StringTokenizer st) throws InlineJavaException { + String class_name = st.nextToken() ; + Class c = ijc.ValidateClass(class_name) ; + + String is_it_a = st.nextToken() ; + Class d = ijc.ValidateClass(is_it_a) ; + + SetResponse(new Integer(ijc.DoesExtend(c, d))) ; + } + + + void ObjectCount(StringTokenizer st) throws InlineJavaException { + SetResponse(new Integer(ijs.ObjectCount())) ; + } + + + /* + Creates a Java Object with the specified arguments. + */ + void CreateJavaObject(StringTokenizer st) throws InlineJavaException { + String class_name = st.nextToken() ; + Class c = ijc.ValidateClass(class_name) ; + + if (! ijc.ClassIsArray(c)){ + ArrayList f = ValidateMethod(true, c, class_name, st) ; + Object p[] = (Object [])f.get(1) ; + Class clist[] = (Class [])f.get(2) ; + + try { + Object o = CreateObject(c, p, clist) ; + SetResponse(o) ; + } + catch (InlineJavaInvocationTargetException ite){ + Throwable t = ite.GetThrowable() ; + if (t instanceof InlineJavaException){ + InlineJavaException ije = (InlineJavaException)t ; + throw ije ; + } + else{ + SetResponse(new InlineJavaThrown(t)) ; + } + } + } + else{ + // Here we send the type of array we want, but CreateArray + // exception the element type. + StringBuffer sb = new StringBuffer(class_name) ; + // Remove the ['s + while (sb.toString().startsWith("[")){ + sb.replace(0, 1, "") ; + } + // remove the L and the ; + if (sb.toString().startsWith("L")){ + sb.replace(0, 1, "") ; + sb.replace(sb.length() - 1, sb.length(), "") ; + } + + Class ec = ijc.ValidateClass(sb.toString()) ; + + InlineJavaUtils.debug(4, "array elements: " + ec.getName()) ; + Object o = ija.CreateArray(ec, st) ; + SetResponse(o) ; + } + } + + + /* + Calls a Java method + */ + void CallJavaMethod(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + String class_name = st.nextToken() ; + Object o = null ; + if (id > 0){ + o = ijs.GetObject(id) ; + + // Use the class sent by Perl (it might be casted) + // class_name = o.getClass().getName() ; + } + + Class c = ijc.ValidateClass(class_name) ; + String method = st.nextToken() ; + + if ((ijc.ClassIsArray(c))&&(method.equals("getLength"))){ + int length = Array.getLength(o) ; + SetResponse(new Integer(length)) ; + } + else{ + ArrayList f = ValidateMethod(false, c, method, st) ; + Method m = (Method)f.get(0) ; + String name = m.getName() ; + Object p[] = (Object [])f.get(1) ; + + try { + Object ret = InlineJavaServer.GetInstance().GetUserClassLoader().invoke(m, o, p) ; + SetResponse(ret, AutoCast(ret, m.getReturnType())) ; + } + catch (IllegalAccessException e){ + throw new InlineJavaException("You are not allowed to invoke method " + name + " in class " + class_name + ": " + e.getMessage()) ; + } + catch (IllegalArgumentException e){ + throw new InlineJavaException("Arguments for method " + name + " in class " + class_name + " are incompatible: " + e.getMessage()) ; + } + catch (InvocationTargetException e){ + Throwable t = e.getTargetException() ; + String type = t.getClass().getName() ; + String msg = t.getMessage() ; + InlineJavaUtils.debug(1, "method " + name + " in class " + class_name + " threw exception " + type + ": " + msg) ; + if (t instanceof InlineJavaException){ + InlineJavaException ije = (InlineJavaException)t ; + throw ije ; + } + else{ + SetResponse(new InlineJavaThrown(t)) ; + } + } + } + } + + + /* + */ + Class AutoCast(Object o, Class want){ + if (o == null){ + return null ; + } + else { + Class got = o.getClass() ; + if (got.equals(want)){ + return null ; + } + else { + boolean _public = (got.getModifiers() & Modifier.PUBLIC) != 0 ; + if ((_public)||(got.getPackage() == null)){ + return null ; + } + else { + InlineJavaUtils.debug(3, "AutoCast: " + got.getName() + " -> " + want.getName()) ; + return want ; + } + } + } + } + + + /* + Returns a new reference to the current object, using the provided subtype + */ + void Cast(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + String class_name = st.nextToken() ; + Object o = ijs.GetObject(id) ; + Class c = ijc.ValidateClass(class_name) ; + + SetResponse(o, c) ; + } + + + /* + */ + void Read(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + int len = Integer.parseInt(st.nextToken()) ; + + Object o = ijs.GetObject(id) ; + Object ret = null ; + try { + ret = InlineJavaHandle.read(o, len) ; + } + catch (java.io.IOException e){ + ret = new InlineJavaThrown(e) ; + } + + SetResponse(ret) ; + } + + + void MakeBuffered(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + Object o = ijs.GetObject(id) ; + Object ret = null ; + try { + ret = InlineJavaHandle.makeBuffered(o) ; + if (ret != o){ + int buf_id = ijs.PutObject(ret) ; + ret = new Integer(buf_id) ; + } + else { + ret = new Integer(id) ; + } + } + catch (java.io.IOException e){ + ret = new InlineJavaThrown(e) ; + } + + SetResponse(ret) ; + } + + + void ReadLine(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + Object o = ijs.GetObject(id) ; + Object ret = null ; + try { + ret = InlineJavaHandle.readLine(o) ; + } + catch (java.io.IOException e){ + ret = new InlineJavaThrown(e) ; + } + + SetResponse(ret) ; + } + + + void Write(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + Object arg = ijc.CastArgument(Object.class, st.nextToken()) ; + + Object o = ijs.GetObject(id) ; + Object ret = null ; + try { + int len = InlineJavaHandle.write(o, arg.toString()) ; + ret = new Integer(len) ; + } + catch (java.io.IOException e){ + ret = new InlineJavaThrown(e) ; + } + + SetResponse(ret) ; + } + + + void Close(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + Object o = ijs.GetObject(id) ; + Object ret = null ; + try { + InlineJavaHandle.close(o) ; + } + catch (java.io.IOException e){ + ret = new InlineJavaThrown(e) ; + } + + SetResponse(ret) ; + } + + + /* + Sets a Java member variable + */ + void SetJavaMember(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + String class_name = st.nextToken() ; + Object o = null ; + if (id > 0){ + o = ijs.GetObject(id) ; + + // Use the class sent by Perl (it might be casted) + // class_name = o.getClass().getName() ; + } + + Class c = ijc.ValidateClass(class_name) ; + String member = st.nextToken() ; + + if (ijc.ClassIsArray(c)){ + int idx = Integer.parseInt(member) ; + Class type = ijc.ValidateClass(st.nextToken()) ; + String arg = st.nextToken() ; + + String msg = "For array of type " + c.getName() + ", element " + member + ": " ; + try { + Object elem = ijc.CastArgument(type, arg) ; + InlineJavaServer.GetInstance().GetUserClassLoader().array_set(o, idx, elem) ; + SetResponse(null) ; + } + catch (InlineJavaCastException e){ + throw new InlineJavaCastException(msg + e.getMessage()) ; + } + catch (InlineJavaException e){ + throw new InlineJavaException(msg + e.getMessage()) ; + } + } + else{ + ArrayList fl = ValidateMember(c, member, st) ; + Field f = (Field)fl.get(0) ; + String name = f.getName() ; + Object p = (Object)fl.get(1) ; + + try { + InlineJavaServer.GetInstance().GetUserClassLoader().set(f, o, p) ; + SetResponse(null) ; + } + catch (IllegalAccessException e){ + throw new InlineJavaException("You are not allowed to set member " + name + " in class " + class_name + ": " + e.getMessage()) ; + } + catch (IllegalArgumentException e){ + throw new InlineJavaException("Argument for member " + name + " in class " + class_name + " is incompatible: " + e.getMessage()) ; + } + } + } + + + /* + Gets a Java member variable + */ + void GetJavaMember(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + String class_name = st.nextToken() ; + Object o = null ; + if (id > 0){ + o = ijs.GetObject(id) ; + + // Use the class sent by Perl (it might be casted) + // class_name = o.getClass().getName() ; + } + + Class c = ijc.ValidateClass(class_name) ; + String member = st.nextToken() ; + + if (ijc.ClassIsArray(c)){ + int idx = Integer.parseInt(member) ; + Object ret = InlineJavaServer.GetInstance().GetUserClassLoader().array_get(o, idx) ; + Class eclass = ijc.ValidateClass(ijc.CleanClassName(class_name.substring(1))) ; + SetResponse(ret, AutoCast(ret, eclass)) ; + } + else{ + ArrayList fl = ValidateMember(c, member, st) ; + + Field f = (Field)fl.get(0) ; + String name = f.getName() ; + try { + Object ret = InlineJavaServer.GetInstance().GetUserClassLoader().get(f, o) ; + SetResponse(ret, AutoCast(ret, f.getType())) ; + } + catch (IllegalAccessException e){ + throw new InlineJavaException("You are not allowed to set member " + name + " in class " + class_name + ": " + e.getMessage()) ; + } + catch (IllegalArgumentException e){ + throw new InlineJavaException("Argument for member " + name + " in class " + class_name + " is incompatible: " + e.getMessage()) ; + } + } + } + + + /* + Deletes a Java object + */ + void DeleteJavaObject(StringTokenizer st) throws InlineJavaException { + int id = Integer.parseInt(st.nextToken()) ; + + Object o = ijs.DeleteObject(id) ; + + SetResponse(null) ; + } + + + /* + Creates a Java Object with the specified arguments. + */ + Object CreateObject(Class p, Object args[], Class proto[]) throws InlineJavaException { + p = ijc.FindWrapper(p) ; + + String name = p.getName() ; + Object ret = null ; + try { + ret = InlineJavaServer.GetInstance().GetUserClassLoader().create(p, args, proto) ; + } + catch (NoSuchMethodException e){ + throw new InlineJavaException("Constructor for class " + name + " with signature " + InlineJavaUtils.CreateSignature(proto) + " not found: " + e.getMessage()) ; + } + catch (InstantiationException e){ + throw new InlineJavaException("You are not allowed to instantiate object of class " + name + ": " + e.getMessage()) ; + } + catch (IllegalAccessException e){ + throw new InlineJavaException("You are not allowed to instantiate object of class " + name + " using the constructor with signature " + InlineJavaUtils.CreateSignature(proto) + ": " + e.getMessage()) ; + } + catch (IllegalArgumentException e){ + throw new InlineJavaException("Arguments to constructor for class " + name + " with signature " + InlineJavaUtils.CreateSignature(proto) + " are incompatible: " + e.getMessage()) ; + } + catch (InvocationTargetException e){ + Throwable t = e.getTargetException() ; + String type = t.getClass().getName() ; + String msg = t.getMessage() ; + throw new InlineJavaInvocationTargetException( + "Constructor for class " + name + " with signature " + InlineJavaUtils.CreateSignature(proto) + " threw exception " + type + ": " + msg, + t) ; + } + + return ret ; + } + + + /* + Makes sure a method exists + */ + ArrayList ValidateMethod(boolean constructor, Class c, String name, StringTokenizer st) throws InlineJavaException { + ArrayList ret = new ArrayList() ; + + // Extract signature + String signature = st.nextToken() ; + + // Extract the arguments + ArrayList args = new ArrayList() ; + while (st.hasMoreTokens()){ + args.add(args.size(), st.nextToken()) ; + } + + String key = c.getName() + "." + name + signature ; + ArrayList ml = new ArrayList() ; + Class params[] = null ; + + Member cached = (Member)member_cache.get(key) ; + if (cached != null){ + InlineJavaUtils.debug(3, "method was cached") ; + ml.add(ml.size(), cached) ; + } + else{ + Member ma[] = (constructor ? (Member [])c.getConstructors() : (Member [])c.getMethods()) ; + for (int i = 0 ; i < ma.length ; i++){ + Member m = ma[i] ; + + if (m.getName().equals(name)){ + InlineJavaUtils.debug(3, "found a " + name + (constructor ? " constructor" : " method")) ; + + if (constructor){ + params = ((Constructor)m).getParameterTypes() ; + } + else{ + params = ((Method)m).getParameterTypes() ; + } + + // Now we check if the signatures match + String sign = InlineJavaUtils.CreateSignature(params, ",") ; + InlineJavaUtils.debug(3, sign + " = " + signature + "?") ; + + if (signature.equals(sign)){ + InlineJavaUtils.debug(3, "has matching signature " + sign) ; + ml.add(ml.size(), m) ; + member_cache.put(key, m) ; + break ; + } + } + } + } + + // Now we got a list of matching methods (actually 0 or 1). + // We have to figure out which one we will call. + if (ml.size() == 0){ + // Nothing matched. Maybe we got a default constructor + if ((constructor)&&(signature.equals("()"))){ + ret.add(0, null) ; + ret.add(1, new Object [] {}) ; + ret.add(2, new Class [] {}) ; + } + else{ + throw new InlineJavaException( + (constructor ? "Constructor " : "Method ") + + name + " for class " + c.getName() + " with signature " + + signature + " not found") ; + } + } + else if (ml.size() == 1){ + // Now we need to force the arguments received to match + // the methods signature. + Member m = (Member)ml.get(0) ; + if (constructor){ + params = ((Constructor)m).getParameterTypes() ; + } + else{ + params = ((Method)m).getParameterTypes() ; + } + + String msg = "In method " + name + " of class " + c.getName() + ": " ; + try { + ret.add(0, m) ; + ret.add(1, ijc.CastArguments(params, args)) ; + ret.add(2, params) ; + } + catch (InlineJavaCastException e){ + throw new InlineJavaCastException(msg + e.getMessage()) ; + } + catch (InlineJavaException e){ + throw new InlineJavaException(msg + e.getMessage()) ; + } + } + + return ret ; + } + + + /* + Makes sure a member exists + */ + ArrayList ValidateMember(Class c, String name, StringTokenizer st) throws InlineJavaException { + ArrayList ret = new ArrayList() ; + + // Extract member type + String type = st.nextToken() ; + + // Extract the argument + String arg = st.nextToken() ; + + String key = type + " " + c.getName() + "." + name ; + ArrayList fl = new ArrayList() ; + Class param = null ; + + Member cached = (Member)member_cache.get(key) ; + if (cached != null){ + InlineJavaUtils.debug(3, "member was cached") ; + fl.add(fl.size(), cached) ; + } + else { + Field fa[] = c.getFields() ; + for (int i = 0 ; i < fa.length ; i++){ + Field f = fa[(InlineJavaUtils.ReverseMembers() ? (fa.length - 1 - i) : i)] ; + + if (f.getName().equals(name)){ + InlineJavaUtils.debug(3, "found a " + name + " member") ; + + param = f.getType() ; + String t = param.getName() ; + if (type.equals(t)){ + InlineJavaUtils.debug(3, "has matching type " + t) ; + fl.add(fl.size(), f) ; + } + } + } + } + + // Now we got a list of matching members. + // We have to figure out which one we will call. + if (fl.size() == 0){ + throw new InlineJavaException( + "Member " + name + " of type " + type + " for class " + c.getName() + + " not found") ; + } + else { + // Now we need to force the arguments received to match + // the methods signature. + + // If we have more that one, we use the last one, which is the most + // specialized + Field f = (Field)fl.get(fl.size() - 1) ; + member_cache.put(key, f) ; + param = f.getType() ; + + String msg = "For member " + name + " of class " + c.getName() + ": " ; + try { + ret.add(0, f) ; + ret.add(1, ijc.CastArgument(param, arg)) ; + ret.add(2, param) ; + } + catch (InlineJavaCastException e){ + throw new InlineJavaCastException(msg + e.getMessage()) ; + } + catch (InlineJavaException e){ + throw new InlineJavaException(msg + e.getMessage()) ; + } + } + + return ret ; + } + + + /* + This sets the response that will be returned to the Perl + script + */ + void SetResponse(Object o) throws InlineJavaException { + SetResponse(o, null) ; + } + + + void SetResponse(Object o, Class p) throws InlineJavaException { + response = "ok " + SerializeObject(o, p) ; + } + + + String SerializeObject(Object o, Class p) throws InlineJavaException { + Class c = (o == null ? null : o.getClass()) ; + + if ((c != null)&&(p != null)){ + if (ijc.DoesExtend(c, p) < 0){ + throw new InlineJavaException("Can't cast a " + c.getName() + " to a " + p.getName()) ; + } + else{ + c = p ; + } + } + + if (o == null){ + return "undef:" ; + } + else if ((ijc.ClassIsNumeric(c))||(ijc.ClassIsChar(c))||(ijc.ClassIsString(c))){ + if ((ijs.GetNativeDoubles())&&(ijc.ClassIsDouble(c))){ + Double d = (Double)o ; + long l = Double.doubleToLongBits(d.doubleValue()) ; + char ca[] = new char[8] ; + for (int i = 0 ; i < 8 ; i++){ + ca[i] = (char)((l >> (8 * i)) & 0xFF) ; + } + return "double:" + Encode(new String(ca)) ; + } + else { + return "scalar:" + Encode(o.toString()) ; + } + } + else if (ijc.ClassIsBool(c)){ + String b = o.toString() ; + return "scalar:" + Encode((b.equals("true") ? "1" : "0")) ; + } + else { + if (! (o instanceof org.perl.inline.java.InlineJavaPerlObject)){ + // Here we need to register the object in order to send + // it back to the Perl script. + boolean thrown = false ; + String type = "object" ; + if (o instanceof InlineJavaThrown){ + thrown = true ; + o = ((InlineJavaThrown)o).GetThrowable() ; + c = o.getClass() ; + } + else if (ijc.ClassIsArray(c)){ + type = "array" ; + } + else if (ijc.ClassIsHandle(c)){ + type = "handle" ; + } + int id = ijs.PutObject(o) ; + + return "java_" + type + ":" + (thrown ? "1" : "0") + ":" + String.valueOf(id) + + ":" + c.getName() ; + } + else { + return "perl_object:" + ((InlineJavaPerlObject)o).GetId() + + ":" + ((InlineJavaPerlObject)o).GetPkg() ; + } + } + } + + + byte[] DecodeToByteArray(String s){ + return InlineJavaUtils.DecodeBase64(s.toCharArray()) ; + } + + + String Decode(String s) throws InlineJavaException { + try { + if (encoding != null){ + return new String(DecodeToByteArray(s), encoding) ; + } + else { + return new String(DecodeToByteArray(s)) ; + } + } + catch (UnsupportedEncodingException e){ + throw new InlineJavaException("Unsupported encoding: " + e.getMessage()) ; + } + } + + + String EncodeFromByteArray(byte bytes[]){ + return new String(InlineJavaUtils.EncodeBase64(bytes)) ; + } + + + String Encode(String s) throws InlineJavaException { + try { + if (encoding != null){ + return EncodeFromByteArray(s.getBytes(encoding)) ; + } + else { + return EncodeFromByteArray(s.getBytes()) ; + } + } + catch (UnsupportedEncodingException e){ + throw new InlineJavaException("Unsupported encoding: " + e.getMessage()) ; + } + } + + + String GetResponse(){ + return response ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaServer.java b/Java/sources/org/perl/inline/java/InlineJavaServer.java new file mode 100644 index 0000000..1c62ef9 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaServer.java @@ -0,0 +1,337 @@ +package org.perl.inline.java ; + +import java.net.* ; +import java.io.* ; +import java.util.* ; + + +/* + This is the server that will answer all the requests for and on Java + objects. +*/ +public class InlineJavaServer { + private static InlineJavaServer instance = null ; + private String host = null ; + private int port = 0 ; + private boolean shared_jvm = false ; + private boolean priv = false ; + private boolean native_doubles = false ; + + private boolean finished = false ; + private ServerSocket server_socket = null ; + private InlineJavaUserClassLoader ijucl = null ; + private HashMap thread_objects = new HashMap() ; + private int objid = 1 ; + private boolean jni = false ; + private Thread creator = null ; + private int thread_count = 0 ; + + + // This constructor is used in JNI mode + private InlineJavaServer(int debug, boolean _native_doubles){ + init(debug, _native_doubles) ; + + jni = true ; + AddThread(creator) ; + } + + + // This constructor is used in server mode + // Normally one would then call RunMainLoop() + /* Note: Consider http://groups.google.com/group/perl.inline/tree/browse_frm/thread/aa7f5ce236f6d576/3db48a308a8175fb?rnum=1&hl=en&q=Congratulations+with+Inline%3A%3AJava+0.51&_done=%2Fgroup%2Fperl.inline%2Fbrowse_frm%2Fthread%2Faa7f5ce236f6d576%2Fd2de9cf38429c09c%3Flnk%3Dst%26q%3DCongratulations+with+Inline%3A%3AJava+0.51%26rnum%3D1%26hl%3Den%26#doc_3db48a308a8175fb before changing this prototype */ + public InlineJavaServer(int debug, String _host, int _port, boolean _shared_jvm, boolean _priv, boolean _native_doubles){ + init(debug, _native_doubles) ; + + jni = false ; + host = _host ; + port = _port ; + shared_jvm = _shared_jvm ; + priv = _priv ; + + try { + if ((host == null)||(host.equals(""))||(host.equals("ANY"))){ + server_socket = new ServerSocket(port) ; + } + else { + server_socket = new ServerSocket(port, 0, InetAddress.getByName(host)) ; + } + } + catch (IOException e){ + InlineJavaUtils.Fatal("Can't open server socket on port " + String.valueOf(port) + + ": " + e.getMessage()) ; + } + } + + + public void RunMainLoop(){ + while (! finished){ + try { + String name = "IJST-#" + thread_count++ ; + InlineJavaServerThread ijt = new InlineJavaServerThread(name, this, server_socket.accept(), + (priv ? new InlineJavaUserClassLoader() : ijucl)) ; + ijt.start() ; + if (! shared_jvm){ + try { + ijt.join() ; + } + catch (InterruptedException e){ + } + break ; + } + } + catch (IOException e){ + if (! finished){ + System.err.println("Main Loop IO Error: " + e.getMessage()) ; + System.err.flush() ; + } + } + } + } + + + private synchronized void init(int debug, boolean _native_doubles){ + instance = this ; + creator = Thread.currentThread() ; + InlineJavaUtils.set_debug(debug) ; + native_doubles = _native_doubles ; + + ijucl = new InlineJavaUserClassLoader() ; + } + + + static InlineJavaServer GetInstance(){ + if (instance == null){ + InlineJavaUtils.Fatal("No instance of InlineJavaServer has been created!") ; + } + + return instance ; + } + + + InlineJavaUserClassLoader GetUserClassLoader(){ + Thread t = Thread.currentThread() ; + if (t instanceof InlineJavaServerThread){ + return ((InlineJavaServerThread)t).GetUserClassLoader() ; + } + else { + return ijucl ; + } + } + + + String GetType(){ + return (shared_jvm ? "shared" : "private") ; + } + + + boolean GetNativeDoubles(){ + return native_doubles ; + } + + + boolean IsJNI(){ + return jni ; + } + + + /* + Since this function is also called from the JNI XS extension, + it's best if it doesn't throw any exceptions. + */ + String ProcessCommand(String cmd) { + return ProcessCommand(cmd, true) ; + } + + + String ProcessCommand(String cmd, boolean addlf) { + InlineJavaUtils.debug(3, "packet recv is " + cmd) ; + + String resp = null ; + if (cmd != null){ + InlineJavaProtocol ijp = new InlineJavaProtocol(this, cmd) ; + try { + ijp.Do() ; + InlineJavaUtils.debug(3, "packet sent is " + ijp.GetResponse()) ; + resp = ijp.GetResponse() ; + } + catch (InlineJavaException e){ + // Encode the error in default encoding since we don't want any + // Exceptions thrown here... + String err = "error scalar:" + ijp.EncodeFromByteArray(e.getMessage().getBytes()) ; + InlineJavaUtils.debug(3, "packet sent is " + err) ; + resp = err ; + } + } + else{ + if (! shared_jvm){ + // Probably connection dropped... + InlineJavaUtils.debug(1, "lost connection with client in single client mode. Exiting.") ; + System.exit(1) ; + } + else{ + InlineJavaUtils.debug(1, "lost connection with client in shared JVM mode.") ; + return null ; + } + } + + if (addlf){ + resp = resp + "\n" ; + } + + return resp ; + } + + + /* + This method really has no business here, but for historical reasons + it will remain here. + */ + native String jni_callback(String cmd) ; + + + boolean IsThreadPerlContact(Thread t){ + if (((jni)&&(t == creator))|| + ((! jni)&&(t instanceof InlineJavaServerThread))){ + return true ; + } + + return false ; + } + + + synchronized Object GetObject(int id) throws InlineJavaException { + Object o = null ; + HashMap h = (HashMap)thread_objects.get(Thread.currentThread()) ; + + if (h == null){ + throw new InlineJavaException("Can't find thread " + Thread.currentThread().getName() + "!") ; + } + else{ + o = h.get(new Integer(id)) ; + if (o == null){ + throw new InlineJavaException("Can't find object " + id + " for thread " +Thread.currentThread().getName()) ; + } + } + + return o ; + } + + + synchronized int PutObject(Object o) throws InlineJavaException { + HashMap h = (HashMap)thread_objects.get(Thread.currentThread()) ; + + int id = objid ; + if (h == null){ + throw new InlineJavaException("Can't find thread " + Thread.currentThread().getName() + "!") ; + } + else{ + h.put(new Integer(objid), o) ; + objid++ ; + } + + return id ; + } + + + synchronized Object DeleteObject(int id) throws InlineJavaException { + Object o = null ; + HashMap h = (HashMap)thread_objects.get(Thread.currentThread()) ; + + if (h == null){ + throw new InlineJavaException("Can't find thread " + Thread.currentThread().getName() + "!") ; + } + else{ + o = h.remove(new Integer(id)) ; + if (o == null){ + throw new InlineJavaException("Can't find object " + id + " for thread " + Thread.currentThread().getName()) ; + } + } + + return o ; + } + + + synchronized int ObjectCount() throws InlineJavaException { + int i = -1 ; + HashMap h = (HashMap)thread_objects.get(Thread.currentThread()) ; + + if (h == null){ + throw new InlineJavaException("Can't find thread " + Thread.currentThread().getName() + "!") ; + } + else{ + i = h.values().size() ; + } + + return i ; + } + + + public synchronized void StopMainLoop(){ + if (! jni){ + try { + finished = true ; + server_socket.close() ; + } + catch (IOException e){ + System.err.println("Shutdown IO Error: " + e.getMessage()) ; + System.err.flush() ; + } + } + } + + + synchronized void Shutdown(){ + StopMainLoop() ; + System.exit(0) ; + } + + + /* + Here the prototype accepts Threads because the JNI thread + calls this method also. + */ + synchronized void AddThread(Thread t){ + thread_objects.put(t, new HashMap()) ; + InlineJavaPerlCaller.AddThread(t) ; + } + + + synchronized void RemoveThread(InlineJavaServerThread t){ + thread_objects.remove(t) ; + InlineJavaPerlCaller.RemoveThread(t) ; + } + + + + /* + Startup + */ + public static void main(String[] argv){ + int debug = Integer.parseInt(argv[0]) ; + String host = argv[1] ; + int port = Integer.parseInt(argv[2]) ; + boolean shared_jvm = new Boolean(argv[3]).booleanValue() ; + boolean priv = new Boolean(argv[4]).booleanValue() ; + boolean native_doubles = new Boolean(argv[5]).booleanValue() ; + + InlineJavaServer ijs = new InlineJavaServer(debug, host, port, shared_jvm, priv, native_doubles) ; + ijs.RunMainLoop() ; + System.exit(0) ; + } + + + /* + With PerlInterpreter this is called twice, but we don't want to create + a new object the second time. + */ + public static InlineJavaServer jni_main(int debug, boolean native_doubles){ + if (instance != null){ + InlineJavaUtils.set_debug(debug) ; + InlineJavaUtils.debug(1, "recycling InlineJavaServer created by PerlInterpreter") ; + return instance ; + } + else { + return new InlineJavaServer(debug, native_doubles) ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaServerThread.java b/Java/sources/org/perl/inline/java/InlineJavaServerThread.java new file mode 100644 index 0000000..e39e4e9 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaServerThread.java @@ -0,0 +1,69 @@ +package org.perl.inline.java ; + +import java.io.* ; +import java.net.* ; +import java.util.* ; + + +class InlineJavaServerThread extends Thread { + private InlineJavaServer ijs ; + private Socket client ; + private BufferedReader br ; + private BufferedWriter bw ; + private InlineJavaUserClassLoader ijucl ; + + + InlineJavaServerThread(String name, InlineJavaServer _ijs, Socket _client, InlineJavaUserClassLoader _ijucl) throws IOException { + super(name) ; + client = _client ; + ijs = _ijs ; + ijucl = _ijucl ; + + InputStreamReader ir = new InputStreamReader(client.getInputStream()) ; + OutputStreamWriter or = new OutputStreamWriter(client.getOutputStream()) ; + br = new BufferedReader(ir) ; + bw = new BufferedWriter(or) ; + } + + + BufferedReader GetReader(){ + return br ; + } + + + BufferedWriter GetWriter(){ + return bw ; + } + + + InlineJavaUserClassLoader GetUserClassLoader(){ + return ijucl ; + } + + + public void run(){ + try { + ijs.AddThread(this) ; + + while (true){ + String cmd = br.readLine() ; + + String resp = ijs.ProcessCommand(cmd) ; + if (resp != null){ + bw.write(resp) ; + bw.flush() ; + } + else { + client.close() ; + break ; + } + } + } + catch (IOException e){ + System.err.println("IO error: " + e.getMessage()) ; + } + finally { + ijs.RemoveThread(this) ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaThrown.java b/Java/sources/org/perl/inline/java/InlineJavaThrown.java new file mode 100644 index 0000000..38bee49 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaThrown.java @@ -0,0 +1,14 @@ +package org.perl.inline.java ; + + +class InlineJavaThrown { + Throwable t ; + + InlineJavaThrown(Throwable _t){ + t = _t ; + } + + Throwable GetThrowable(){ + return t ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaUserClassLink.java b/Java/sources/org/perl/inline/java/InlineJavaUserClassLink.java new file mode 100644 index 0000000..0f2d97a --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaUserClassLink.java @@ -0,0 +1,45 @@ +import java.util.* ; +import java.lang.reflect.* ; + + +public class InlineJavaUserClassLink { + public InlineJavaUserClassLink(){ + } + + + public Object invoke(Method m, Object o, Object p[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + return m.invoke(o, p) ; + } + + + public Object get(Field f, Object o) throws IllegalAccessException, IllegalArgumentException { + return f.get(o) ; + } + + + public void set(Field f, Object o, Object p) throws IllegalAccessException, IllegalArgumentException { + f.set(o, p) ; + } + + + public Object array_get(Object o, Integer idx){ + return Array.get(o, idx.intValue()) ; + } + + + public void array_set(Object o, Integer idx, Object elem) throws IllegalArgumentException { + Array.set(o, idx.intValue(), elem) ; + } + + + public Object create(Class p, Object args[], Class proto[]) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // This will allow usage of the default no-arg constructor + if (proto.length == 0){ + return p.newInstance() ; + } + else{ + Constructor con = (Constructor)p.getConstructor(proto) ; + return con.newInstance(args) ; + } + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaUserClassLoader.java b/Java/sources/org/perl/inline/java/InlineJavaUserClassLoader.java new file mode 100644 index 0000000..0105872 --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaUserClassLoader.java @@ -0,0 +1,192 @@ +package org.perl.inline.java ; + +import java.net.* ; +import java.util.* ; +import java.io.* ; +import java.lang.reflect.* ; + + +/* + This is the ClassLoader that loads the users code. It is also + used to pass reflection calls to the InlineJavaUserClassLink + so that it will execute them. +*/ +class InlineJavaUserClassLoader extends URLClassLoader { + private HashMap urls = new HashMap() ; + + private Object link = null ; + private Method invoke = null ; + private Method get = null ; + private Method set = null ; + private Method array_get = null ; + private Method array_set = null ; + private Method create = null ; + + + public InlineJavaUserClassLoader(){ + // Added Thread.currentThread().getContextClassLoader() so that the code works + // in Tomcat and possibly other embedded environments asa well. + super(new URL [] {}, Thread.currentThread().getContextClassLoader()) ; + } + + + public void AddClassPath(String path) throws InlineJavaException { + try { + File p = new File(path) ; + URL u = p.toURI().toURL() ; + if (urls.get(u) == null){ + urls.put(u, "1") ; + addURL(u) ; + InlineJavaUtils.debug(2, "added " + u + " to classpath") ; + } + } + catch (MalformedURLException e){ + throw new InlineJavaException("Can't add invalid classpath entry '" + path + "'") ; + } + } + + + synchronized private void check_link() throws InlineJavaException { + if (link == null){ + try { + InlineJavaUtils.debug(1, "loading InlineJavaUserClassLink via InlineJavaUserClassLoader") ; + Class c = Class.forName("InlineJavaUserClassLink", true, this) ; + link = c.newInstance() ; + + invoke = find_method(c, "invoke") ; + get = find_method(c, "get") ; + set = find_method(c, "set") ; + array_get = find_method(c, "array_get") ; + array_set = find_method(c, "array_set") ; + create = find_method(c, "create") ; + } + catch (Exception e){ + throw new InlineJavaException("InlineJavaUserClassLoader can't load InlineJavaUserClassLink: invalid classpath setup (" + + e.getClass().getName() + ": " + e.getMessage() + ")") ; + } + } + } + + + private Method find_method(Class c, String name) throws InlineJavaException { + Method ml[] = c.getMethods() ; + for (int i = 0 ; i < ml.length ; i++){ + if (ml[i].getName().equals(name)){ + return ml[i] ; + } + } + + throw new InlineJavaException("Can't find method '" + name + + "' in class InlineJavaUserClassLink") ; + } + + + private Object invoke_via_link(Method m, Object p[]) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InlineJavaException { + try { + return m.invoke(link, p) ; + } + catch (IllegalAccessException e){ + throw new InlineJavaException("Can't invoke method from class InlineJavaUserClassLink: IllegalAccessException") ; + } + catch (IllegalArgumentException e){ + throw new InlineJavaException("Can't invoke method from class InlineJavaUserClassLink: IllegalArgumentException") ; + } + catch (InvocationTargetException e){ + Throwable t = e.getTargetException() ; + if (t instanceof NoSuchMethodException){ + throw (NoSuchMethodException)t ; + } + else if (t instanceof InstantiationException){ + throw (InstantiationException)t ; + } + else if (t instanceof IllegalAccessException){ + throw (IllegalAccessException)t ; + } + if (t instanceof IllegalAccessException){ + throw (IllegalAccessException)t ; + } + else if (t instanceof IllegalArgumentException){ + throw (IllegalArgumentException)t ; + } + else if (t instanceof InvocationTargetException){ + throw (InvocationTargetException)t ; + } + // Not sure if this is really necessary, but... + else if (t instanceof RuntimeException){ + RuntimeException re = (RuntimeException)t ; + throw re ; + } + else{ + // In theory this case is impossible. + throw new InlineJavaException("Unexpected exception of type '" + + t.getClass().getName() + "': " + t.getMessage()) ; + } + } + } + + + public Object invoke(Method m, Object o, Object p[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InlineJavaException { + check_link() ; + try { + return invoke_via_link(invoke, new Object [] {m, o, p}) ; + } + catch (NoSuchMethodException me){/* Impossible */} + catch (InstantiationException ie){/* Impossible */} + return null ; + } + + + public Object get(Field f, Object o) throws IllegalAccessException, IllegalArgumentException, InlineJavaException { + check_link() ; + try { + return invoke_via_link(get, new Object [] {f, o}) ; + } + catch (NoSuchMethodException me){/* Impossible */} + catch (InstantiationException ie){/* Impossible */} + catch (InvocationTargetException e){/* Impossible */} + return null ; + } + + + public void set(Field f, Object o, Object p) throws IllegalAccessException, IllegalArgumentException, InlineJavaException { + check_link() ; + try { + invoke_via_link(set, new Object [] {f, o, p}) ; + } + catch (NoSuchMethodException me){/* Impossible */} + catch (InstantiationException ie){/* Impossible */} + catch (InvocationTargetException e){/* Impossible */} + } + + + public Object array_get(Object o, int idx) throws InlineJavaException { + check_link() ; + try { + return invoke_via_link(array_get, new Object [] {o, new Integer(idx)}) ; + } + catch (NoSuchMethodException me){/* Impossible */} + catch (InstantiationException ie){/* Impossible */} + catch (IllegalAccessException iae){/* Impossible */} + catch (IllegalArgumentException iae){/* Impossible */} + catch (InvocationTargetException e){/* Impossible */} + return null ; + } + + + public void array_set(Object o, int idx, Object elem) throws IllegalArgumentException, InlineJavaException { + check_link() ; + try { + invoke_via_link(array_set, new Object [] {o, new Integer(idx), elem}) ; + } + catch (NoSuchMethodException me){/* Impossible */} + catch (InstantiationException ie){/* Impossible */} + catch (IllegalAccessException iae){/* Impossible */} + catch (InvocationTargetException e){/* Impossible */} + } + + + public Object create(Class p, Object args[], Class proto[]) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InlineJavaException { + check_link() ; + return invoke_via_link(create, new Object [] {p, args, proto}) ; + } +} diff --git a/Java/sources/org/perl/inline/java/InlineJavaUtils.java b/Java/sources/org/perl/inline/java/InlineJavaUtils.java new file mode 100644 index 0000000..65426dd --- /dev/null +++ b/Java/sources/org/perl/inline/java/InlineJavaUtils.java @@ -0,0 +1,175 @@ +package org.perl.inline.java ; + +import java.util.* ; + + +/* + Creates a string representing a method signature +*/ +class InlineJavaUtils { + private static int debug = 0 ; + + + public synchronized static void set_debug(int d){ + debug = d ; + } + + + public static int get_debug(){ + return debug ; + } + + + static String CreateSignature(Class param[]){ + return CreateSignature(param, ", ") ; + } + + + static String CreateSignature(Class param[], String del){ + StringBuffer ret = new StringBuffer() ; + for (int i = 0 ; i < param.length ; i++){ + if (i > 0){ + ret.append(del) ; + } + ret.append(param[i].getName()) ; + } + + return "(" + ret.toString() + ")" ; + } + + + synchronized static void debug(int level, String s) { + if ((debug > 0)&&(debug >= level)){ + StringBuffer sb = new StringBuffer() ; + for (int i = 0 ; i < level ; i++){ + sb.append(" ") ; + } + System.err.println("[java][" + level + "]" + sb.toString() + s) ; + System.err.flush() ; + } + } + + + static void Fatal(String msg){ + System.err.println(msg) ; + System.err.flush() ; + System.exit(1) ; + } + + + static boolean ReverseMembers() { + String v = System.getProperty("java.version") ; + boolean no_rev = ((v.startsWith("1.2"))||(v.startsWith("1.3"))) ; + + return (! no_rev) ; + } + + + + /* + Base64 stuff. This section conatins code by Christian d'Heureuse that is + licended under the LGPL. Used by permission: + + From: Christian d'Heureuse <chdh@inventec.ch> + To: Patrick LeBoutillier <patrick.leboutillier@gmail.com> + Date: Aug 11, 2005 4:45 AM + Subject: Re: Base64Coder + + > I was wondering if you can grant me permission to include your + > code in my project. + + Yes, I grant you permission to include the Base64Coder class in your + project. + + * + * A Base64 Encoder/Decoder. + * + * This class is used to encode and decode data in Base64 format + * as described in RFC 1521. + * + * <p> + * Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland.<br> + * License: This is "Open Source" software and released under the <a href="http://www.gnu.org/licenses/lgpl.html" target="_top">GNU/LGPL</a> license. + * It is provided "as is" without warranty of any kind. Please contact the author for other licensing arrangements.<br> + * Home page: <a href="http://www.source-code.biz" target="_top">www.source-code.biz</a><br> + * + * <p> + * Version history:<br> + * 2003-07-22 Christian d'Heureuse (chdh): Module created.<br> + * 2005-08-11 chdh: Lincense changed from GPL to LGPL. + * + */ + + // Mapping table from 6-bit nibbles to Base64 characters. + private static char[] map1 = new char[64]; + static { + int i=0; + for (char c='A'; c<='Z'; c++) map1[i++] = c; + for (char c='a'; c<='z'; c++) map1[i++] = c; + for (char c='0'; c<='9'; c++) map1[i++] = c; + map1[i++] = '+'; map1[i++] = '/'; + } + + // Mapping table from Base64 characters to 6-bit nibbles. + private static byte[] map2 = new byte[128]; + static { + for (int i=0; i<map2.length; i++) map2[i] = -1; + for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; + } + + + public static char[] EncodeBase64(byte[] in){ + int iLen = in.length; + int oDataLen = (iLen*4+2)/3; // output length without padding + int oLen = ((iLen+2)/3)*4; // output length including padding + char[] out = new char[oLen]; + int ip = 0; + int op = 0; + while (ip < iLen) { + int i0 = in[ip++] & 0xff; + int i1 = ip < iLen ? in[ip++] & 0xff : 0; + int i2 = ip < iLen ? in[ip++] & 0xff : 0; + int o0 = i0 >>> 2; + int o1 = ((i0 & 3) << 4) | (i1 >>> 4); + int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); + int o3 = i2 & 0x3F; + out[op++] = map1[o0]; + out[op++] = map1[o1]; + out[op] = op < oDataLen ? map1[o2] : '='; op++; + out[op] = op < oDataLen ? map1[o3] : '='; op++; + } + return out; + } + + + public static byte[] DecodeBase64(char[] in){ + int iLen = in.length; + if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4."); + while (iLen > 0 && in[iLen-1] == '=') iLen--; + int oLen = (iLen*3) / 4; + byte[] out = new byte[oLen]; + int ip = 0; + int op = 0; + while (ip < iLen) { + int i0 = in[ip++]; + int i1 = in[ip++]; + int i2 = ip < iLen ? in[ip++] : 'A'; + int i3 = ip < iLen ? in[ip++] : 'A'; + if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) + throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); + int b0 = map2[i0]; + int b1 = map2[i1]; + int b2 = map2[i2]; + int b3 = map2[i3]; + if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) + throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); + int o0 = ( b0 <<2) | (b1>>>4); + int o1 = ((b1 & 0xf)<<4) | (b2>>>2); + int o2 = ((b2 & 3)<<6) | b3; + out[op++] = (byte)o0; + if (op<oLen) out[op++] = (byte)o1; + if (op<oLen) out[op++] = (byte)o2; + } + return out; + } +} diff --git a/Java/typemap b/Java/typemap new file mode 100755 index 0000000..b470113 --- /dev/null +++ b/Java/typemap @@ -0,0 +1,23 @@ +TYPEMAP +InlineJavaJNIVM * T_PTROBJ_IJVM + + +OUTPUT + +T_PTROBJ_IJVM + sv_setref_pv($arg, \"Inline::Java::JNI\", (void *)$var) ; + + +INPUT + +T_PTROBJ_IJVM + if (sv_derived_from($arg, \"Inline::Java::JNI\")) { + $var = ($type)SvIV((SV*)SvRV($arg)) ; + } + else{ + if (SvOK($arg)){ + croak(\"$var is not of type Inline::Java::JNI\") ; + } + } + + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..bd7d8ca --- /dev/null +++ b/MANIFEST @@ -0,0 +1,81 @@ +CHANGES +MANIFEST +README +README.JNI +TODO +META.yml +Makefile.PL +Java.pm +Java.pod +Java/Object.pm +Java/Protocol.pm +Java/Class.pm +Java/Callback.pm +Java/Callback.pod +Java/Portable.pm +Java/Array.pm +Java/Handle.pm +Java/Makefile.PL +Java/JVM.pm +Java/Server.pm +Java/JNI.pm +Java/JNI.xs +Java/typemap +Java/jvm.def +Java/sources/org/perl/inline/java/InlineJavaArray.java +Java/sources/org/perl/inline/java/InlineJavaCastException.java +Java/sources/org/perl/inline/java/InlineJavaClass.java +Java/sources/org/perl/inline/java/InlineJavaException.java +Java/sources/org/perl/inline/java/InlineJavaInvocationTargetException.java +Java/sources/org/perl/inline/java/InlineJavaPerlCaller.java +Java/sources/org/perl/inline/java/InlineJavaPerlException.java +Java/sources/org/perl/inline/java/InlineJavaProtocol.java +Java/sources/org/perl/inline/java/InlineJavaServer.java +Java/sources/org/perl/inline/java/InlineJavaServerThread.java +Java/sources/org/perl/inline/java/InlineJavaThrown.java +Java/sources/org/perl/inline/java/InlineJavaUserClassLink.java +Java/sources/org/perl/inline/java/InlineJavaUserClassLoader.java +Java/sources/org/perl/inline/java/InlineJavaUtils.java +Java/sources/org/perl/inline/java/InlineJavaCallback.java +Java/sources/org/perl/inline/java/InlineJavaCallbackQueue.java +Java/sources/org/perl/inline/java/InlineJavaPerlNatives.java +Java/sources/org/perl/inline/java/InlineJavaPerlInterpreter.java +Java/sources/org/perl/inline/java/InlineJavaPerlObject.java +Java/sources/org/perl/inline/java/InlineJavaHandle.java +Java/PerlNatives/Makefile.PL +Java/PerlNatives/PerlNatives.pm +Java/PerlNatives/PerlNatives.xs +Java/PerlNatives/PerlNatives.pod +Java/PerlNatives/t/01_init.t +Java/PerlNatives/t/02_perl_natives.t +Java/PerlInterpreter/Makefile.PL +Java/PerlInterpreter/PerlInterpreter.pm +Java/PerlInterpreter/PerlInterpreter.xs +Java/PerlInterpreter/PerlInterpreter.pod +Java/PerlInterpreter/t/01_init.t +Java/PerlInterpreter/t/02_perl_interpreter.t +Java/PerlInterpreter/t/Tests.pl +t/01_init.t +t/02_primitives.t +t/02_primitives_1_4.t +t/03_objects.t +t/04_members.t +t/05_arrays.t +t/06_static.t +t/07_polymorph.t +t/08_study.t +t/09_usages.t +t/10_1_shared_alone.t +t/10_5_shared_fork.t +t/10_6_shared_sim.t +t/11_exceptions.t +t/12_1_callbacks.t +t/13_handles.t +t/14_encoding.t +t/15_native_doubles.t +t/99_end.t +t/types.java +t/types.class +t/no_const.java +t/no_const.class +t/shared.java diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..c36ff7d --- /dev/null +++ b/META.yml @@ -0,0 +1,16 @@ +--- #YAML:1.0 +name: Inline-Java +version: 0.53 +abstract: Write Perl classes in Java. +license: ~ +author: + - Patrick LeBoutillier <patl@cpan.org> +generated_by: ExtUtils::MakeMaker version 6.42 +distribution_type: module +requires: + Inline: 0.44 + MIME::Base64: 0 + Test: 1.13 +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.3.html + version: 1.3 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..6ed46c5 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,299 @@ +use ExtUtils::MakeMaker ; + +use strict ; +use File::Spec ; +use Cwd ; +use Config ; + +require "Java/Portable.pm" ; + +print "\nWelcome to the Inline::Java installation procedure.\n\n" ; + +# Hide PerlNatives by default... +$main::build_perl_natives = 0 ; + +# Grab the J2SDK argument +my $jdk_dir = '' ; +for (my $i = 0 ; $i < scalar(@ARGV) ; $i++){ + my $remove = 0 ; + if ($ARGV[$i] =~ /^J2SDK=(.+)$/){ + $jdk_dir = $1 ; + $remove = 1 ; + } + elsif ($ARGV[$i] =~ /^BUILD_JNI=(.+)$/){ + $main::build_jni = $1 ; + $remove = 1 ; + } + elsif ($ARGV[$i] =~ /^BUILD_PERL_NATIVES=(.+)$/){ + $main::build_perl_natives = $1 ; + $remove = 1 ; + } + elsif ($ARGV[$i] =~ /^BUILD_PERL_INTERPRETER=(.+)$/){ + $main::build_perl_interpreter = $1 ; + $remove = 1 ; + } + elsif ($ARGV[$i] =~ /^JVM_LIB_TYPE=(.+)$/){ + $main::jvm_lib_type = $1 ; + $remove = 1 ; + } + if ($remove){ + splice(@ARGV, $i, 1) ; + $i-- ; + } +} + +if (! $jdk_dir){ + my $try = $ENV{PERL_INLINE_JAVA_J2SDK} || $ENV{JAVA_HOME} + || Inline::Java::Portable::portable('DEFAULT_J2SDK_DIR') ; + print "Using $try as J2SDK directory.\n\n" if $try ; + $jdk_dir = $try ; +} + +if (! $jdk_dir){ + my $def_pl = File::Spec->catfile('Java', 'default_j2sdk.pl') ; + if (-e $def_pl){ + require File::Spec->catfile('Java', 'default_j2sdk.pl') ; + $jdk_dir = Inline::Java::get_default_j2sdk() ; + } + else { + print <<NO_J2SDK; +A Java 2 SDK is required to install and use Inline::Java. Please +specify your Java 2 SDK installation directory using the J2SDK +option to Makefile.PL as such: + + perl Makefile.PL J2SDK=/path/to/your/j2sdk/installation + +You can set the JAVA_HOME environment variable to specify your +Java 2 SDK installation directory. For example, if you are using +the CPAN installer you can do: + + JAVA_HOME=/path/to/your/j2sdk/installation cpan Inline::Java +NO_J2SDK + exit(1) ; + } +} +elsif (! -d $jdk_dir){ + print <<BAD_J2SDK; +Java 2 SDK installation directory '$jdk_dir' does not exist. +BAD_J2SDK + exit(1) ; +} +my $perl_jdk_dir = $jdk_dir ; +$perl_jdk_dir =~ s/'/\'/g ; + +# Check directory +my $jdk_bin = Inline::Java::Portable::portable("J2SDK_BIN") ; +my $ext = Inline::Java::Portable::portable('EXE_EXTENSION') ; +foreach my $f ('javac', 'jar', 'java'){ + if (! -f File::Spec->catfile($jdk_dir, $jdk_bin, $f . $ext)){ + my $bf = File::Spec->catfile($jdk_bin, $f . $ext) ; + print "Can't locate file '$bf' anywhere under '$jdk_dir'\n" ; + } +} + + +# Now we have the J2SDK directory and it exists. +# We will create the default_j2sdk.pl file that +# will contain that value for future use. +my $def_jdk = File::Spec->catfile('Java', 'default_j2sdk.pl') ; +open(J2SDK, ">$def_jdk") or + die("Can't open '$def_jdk' for writing: $!") ; +print J2SDK <<J2SDK_PL; +# This file is created by the Makefile.PL for Inline::Java +# You can modify it if you wish +use strict ; + +# The default J2SDK to use for Inline::Java. You can change +# it if this value becomes invalid. +sub Inline::Java::get_default_j2sdk { + return '$perl_jdk_dir' ; +} +1 ; + + +J2SDK_PL +close(J2SDK) ; + +print <<SAVE_J2SDK; +Default J2SDK for Inline::Java will be '$jdk_dir'. +See module documentation for information on how to use a different J2SDK +or change this default value. + +SAVE_J2SDK + + +# We will now add the building of our Java files to the Makefile. +my $javac = File::Spec->catfile($jdk_dir, $jdk_bin, 'javac' . $ext) ; +my $jar = File::Spec->catfile($jdk_dir, $jdk_bin, 'jar' . $ext) ; +my $src_dir = File::Spec->catdir('Java', 'sources', 'org', 'perl', 'inline', 'java') ; +my $src = File::Spec->catfile($src_dir, '*.java') ; +my $obj_dir = File::Spec->catdir('Java', 'classes') ; +my $server_arch = File::Spec->catfile('Java', 'InlineJavaServer.jar') ; +my $user_arch = File::Spec->catfile('Java', 'InlineJavaUser.jar') ; + +# Create the object diretory because later we need to put the properties +# file inside it. +if (! -e $obj_dir){ + mkdir($obj_dir) or + die("Can't create object directory '$obj_dir': $!") ; +} + +sub MY::top_targets { + my $this = shift ; + + my $make = <<MAKE ; +# Added by Inline::Java installation +pure_all :: java +MAKE + + return $make . $this->MM::top_targets() ; +} + + +my $INSTALLSITEARCH = '' ; +my $INST_ARCHLIB = '' ; +sub MY::postamble { + my $this = shift ; + + my $java_src = join(' ', glob($src), File::Spec->catfile($obj_dir, 'InlineJava.properties')) ; + + my $make = <<MAKE ; +# Added by Inline::Java installation +JAVA_SRC=$java_src + +java.ts: \$(JAVA_SRC) + \@\$(MKPATH) $obj_dir + "$javac" -deprecation -g -d $obj_dir $src + "$jar" cf $server_arch -C $obj_dir org -C $obj_dir InlineJava.properties + "$jar" cf $user_arch -C $obj_dir InlineJavaUserClassLink.class + \@\$(TOUCH) java.ts + +java :: java.ts +MAKE + + # Used for PerlNatives + $INSTALLSITEARCH = expand_macros($this, 'INSTALLSITEARCH') ; + $INST_ARCHLIB = expand_macros($this, 'INST_ARCHLIB') ; + + return $make ; +} + + +sub expand_macros { + my $mm = shift ; + my $var = shift ; + + my $val = $mm->{$var} ; + while ($val =~ s/\$\((.*?)\)/$mm->{$1}/){} + $val =~ s/\\/\\\\/g ; + + return $val ; +} + + +# Write the Makefile +my $natives_test = File::Spec->catdir('Java', 'Natives', '_Inline_test') ; +my $perlinterp_test = File::Spec->catdir('Java', 'PerlInterpreter', '_Inline_test') ; +WriteMakefile( + NAME => 'Inline::Java', + VERSION_FROM => 'Java.pm', + DIR => ['Java'], + PREREQ_PM => { + Inline => 0.44, + Test => 1.13, + 'MIME::Base64' => 0, + }, + PM => { + 'Java.pm' => File::Spec->catfile('$(INST_LIBDIR)', 'Java.pm'), + 'Java.pod'=> File::Spec->catfile('$(INST_LIBDIR)', 'Java.pod'), + $server_arch => File::Spec->catfile('$(INST_LIBDIR)', $server_arch), + $user_arch => File::Spec->catfile('$(INST_LIBDIR)', $user_arch), + }, + clean => {FILES => "$def_jdk _Inline_test $natives_test $perlinterp_test $obj_dir $server_arch $user_arch java.ts"}, + ($] >= 5.005 ? ## Add these new keywords supported since 5.005 + (ABSTRACT_FROM => 'Java.pod', # retrieve abstract from module + AUTHOR => 'Patrick LeBoutillier <patl@cpan.org>') : ()), +) ; + + +# Add the so_dirs to the default_j2sdk.pl file. +open(J2SDK, ">>$def_jdk") or + die("Can't open '$def_jdk' for appending: $!") ; +print J2SDK <<J2SDK_PL; +sub Inline::Java::get_default_j2sdk_so_dirs { + return ( +J2SDK_PL +foreach my $d (@main::SO_DIRS){ + $d =~ s/'/\'/g ; + print J2SDK "\t\t'$d',\n" ; +} +print J2SDK <<J2SDK_PL; + ) ; +} + + +1 ; +J2SDK_PL +close(J2SDK) ; + + + +# Create the properties that will be included in the jar. +my @perlnatives_so_parts = ("auto", "Inline", "Java", "PerlNatives", + "PerlNatives." . Inline::Java::Portable::portable('SO_EXT')) ; +my $install_perlnatives_so = File::Spec->catfile($INSTALLSITEARCH, @perlnatives_so_parts) ; +$install_perlnatives_so = Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", $install_perlnatives_so) ; +$install_perlnatives_so =~ s/\\/\\\\/g ; +my $test_perlnatives_so = File::Spec->rel2abs(File::Spec->catfile($INST_ARCHLIB, @perlnatives_so_parts)) ; +$test_perlnatives_so = Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", $test_perlnatives_so) ; +$test_perlnatives_so =~ s/\\/\\\\/g ; + +my @perlinterpreter_so_parts = ("auto", "Inline", "Java", "PerlInterpreter", + "PerlInterpreter." . Inline::Java::Portable::portable('SO_EXT')) ; +my $install_perlinterpreter_so = File::Spec->catfile($INSTALLSITEARCH, @perlinterpreter_so_parts) ; +$install_perlinterpreter_so = Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", $install_perlinterpreter_so) ; +$install_perlinterpreter_so =~ s/\\/\\\\/g ; +my $test_perlinterpreter_so = File::Spec->rel2abs(File::Spec->catfile($INST_ARCHLIB, @perlinterpreter_so_parts)) ; +$test_perlinterpreter_so = Inline::Java::Portable::portable("SUB_FIX_JAVA_PATH", $test_perlinterpreter_so) ; +$test_perlinterpreter_so =~ s/\\/\\\\/g ; + +my $libperl = $Config{libperl} ; +my $dlext = $Config{dlext} ; +my $libperl_so = '' ; +if ($libperl =~ /\.$dlext$/){ + $libperl_so = File::Spec->catfile($Config{installarchlib}, 'CORE', $libperl) ; +} + +my $prop = File::Spec->catfile($obj_dir, 'InlineJava.properties') ; +open(PROP, ">$prop") or + die("Can't open '$prop' for writing: $!") ; +print PROP <<PROP; +# This file is created by the Makefile.PL for Inline::Java +inline_java_perlnatives_so_install = $install_perlnatives_so +inline_java_perlnatives_so_test = $test_perlnatives_so +inline_java_perlinterpreter_so_install = $install_perlinterpreter_so +inline_java_perlinterpreter_so_test = $test_perlinterpreter_so +inline_java_libperl_so = $libperl_so +PROP +close(PROP) ; + +# Clean up the Makefile for Win95/98/Me +if (Inline::Java::Portable::portable('COMMAND_COM')){ + print "\nFixing Makefile for Win95/98/Me...\n" ; + open(MAKEFILE, "<Makefile") or die "Can't open Makefile for reading" ; + my @lines = <MAKEFILE> ; + close(MAKEFILE) ; + open(MAKEFILE, ">Makefile") or die "Can't open Makefile for writing" ; + foreach my $line (@lines){ + if ($line !~ /^\s*((\@\[)|(\]))\s*$/){ + print MAKEFILE $line ; + } + } + close(MAKEFILE) ; +} + +my $make = Inline::Java::Portable::portable('MAKE') ; +print "\nYou can continue the installation with the following commands:\n" ; +print " % $make\n" ; +print " % $make test\n" ; +print " % $make install\n" ; @@ -0,0 +1,90 @@ +INTRODUCTION: + +Inline::Java - Write Perl classes in Java. + +Inline::Java lets you write Perl classes in Java. + +Example: + + use Inline Java => <<'END'; + class JAxH { + public JAxH(String x){ + System.out.println("Just Another " + x + " Hacker") ; + } + } + END + + new JAxH('Inline') ; + +When run, this complete program prints: + + Just Another Inline Hacker + + +------------------------------------------------------------------------------- +JNI (JAVA NATIVE INTERFACE) EXTENSION: + +Inline::Java now provides a JNI extension that allows you to load the Java +virtual machine as shared object instead of running it as a separate process. + +See README.JNI for more information on building the JNI extension. + + +------------------------------------------------------------------------------- +INSTALLATION: + +- This module requires Inline version 0.44 or higher to be installed. +- It also requires a version of the Java SDK 1.2 or higher to be + installed. You can get the latest Java SDK from Sun Microsystems + at http://java.sun.com. Follow the provided instructions in order + to install the Java SDK properly. + +To install Inline::Java do this: + + % perl Makefile.PL J2SDK=/your/java/dir (see Note 1) + % make (see Note 2) + % make test (see Note 3, 4) + % make install + +You have to 'make install' before you can run it successfully. + +Note 1: Under Win95/98/Me, you may need to do 'cd ..' to get back to the +your original directory after the command has completed. Also, you may set +either the JAVA_HOME or the PERL_INLINE_JAVA_J2SDK environment variable to +/your/java/dir instead of using the J2SDK Makefile.PL parameter. + +Note 2: Use nmake on Win32. + +Note 3: If you have built the JNI extension and want the test suite to use +it, you will need to set the PERL_INLINE_JAVA_JNI environment variable to 1 +BEFORE running 'make test'. + +Note 4: When testing Inline::Java, it's always a good idea to run 'make test' +twice. The first time you test the building and loading of a module, the +second time you test loading of an already built module. + + +------------------------------------------------------------------------------- +FEATURES FOR THIS VERSION: + +Inline::Java version 0.52 is a minor upgrade that includes: + - Fixed JNI on cygwin (many thanks to Eric Rybski for the patch) + - Improved installation. 'make java' is now performed automatically. + - Fixed problems with disappearing exceptions by localizing $@. + - Other minor bug fixes. + +See CHANGES for a full change list. + +------------------------------------------------------------------------------- +INFORMATION: + += For more information on Inline::Java, see 'perldoc Inline::Java'. += For information about Inline, see 'perldoc Inline'. += For information on using Java, visit http://java.sun.org. + +Inline::Java's mailing list is inline@perl.org. +To subscribe, send an email to inline-subscribe@perl.org. + +Please send questions and comments to Patrick LeBoutillier <patl@cpan.org>. + +Copyright (c) 2001-2005, Patrick LeBoutillier. All Rights Reserved. diff --git a/README.JNI b/README.JNI new file mode 100644 index 0000000..7bc6ad1 --- /dev/null +++ b/README.JNI @@ -0,0 +1,177 @@ +JNI (JAVA NATIVE INTERFACE) EXTENSION +------------------------------------- +Inline::Java now provides a JNI extension that allows you to load the Java +virtual machine as shared object instead of running it as a separate process. + + +PLATFORM AVAILABILITY +--------------------- +The JNI extension is available on all supported platforms. + +The extension builds properly on all platform, but problems can occur when +running it or tweaking maybe necessary on certain platforms. All help +is welcome if anyone out there is a JNI expert. + +The reason why JNI is a bit complex under Linux/Solaris is because of +threads. The Java Virtual Machine (libjvm.so) shared object uses native +threads when embedded inside another program and that host program (in +this case Perl) must link with the same threads library for everything +to work properly. Starting with Perl 5.8, this works fine. With previous +versions, you may get around rebuilding Perl by setting LD_PRELOAD. +The only problems encountered where that when setting LD_PRELOAD before +running the test suite, the LD_PRELOAD affects make as well and on Solaris +some crashes were seen. Read more on this in the Solaris section below. + +Note: Make sure the directories listed at the end of the installation +procedure are included in your LD_LIBRARY_PATH (PATH on Win32) environment +variable. This JNI extension will not load properly the the Java shared +objects cannot be located at runtime. + + +OVERVIEW +-------- + +----------+------------+------------+ + | JDK1.3.1 | J2SDK1.4.2 | J2SDK1.5.0 | ++-------------+----------+------------+------------+ +| Win32 | ok | ok | ok | ++-------------+----------+------------+------------+ +| cygwin | ok* | ok* | ok* | ++-------------+----------+------------+------------+ +| Linux RH7.3 | ok* | ok* | ? | ++-------------+----------+------------+------------+ +| Linux RH9.0 | ok* | ok* | ok | ++-------------+----------+------------+------------+ +| Soloris 2.8 | ok* | ? | ? | ++-------------+----------+------------+------------+ +* : Read below + + +WIN32 +----- +Java 2 SDK 1.3.1: + The JNI extension runs without problems. + +Java 2 SDK 1.4.2: + The JNI extension runs without problems. + + +LINUX +----- +Java 2 SDK 1.3.1: + The JNI extension runs without problems with this Java 2 SDK, provided +that you use Perl >= 5.8.0 or do one of the following: + +1- Rebuild perl and add the libpthread library in front of all other + libraries (see the 'BUILDING PERL' section below). You should also + use your system's malloc (not perl's). + This is the recommended solution. + +2- Add the proper version of libpthread.so to your LD_PRELOAD environment + variable. On my test system this was /lib/i686/libpthread.so.0. + This solution seems viable but thread related crashes/hang-ups have + been reported on some systems. If make crashes while running the test + suite, try running it like this: + % for i in `ls t/*.t` ; do perl -Mblib $i; done + +Also, make sure you use do not use 'classic' VM. This one should only +be used with 'green threads', which don't seem to work with JNI. Use either +the 'client' or the 'server' VMs. + +Java 2 SDK 1.4.2: + The same as Java 2 SDK 1.3.1 applies. + + +SOLARIS +------- +Java 2 SDK 1.3.1: + The JNI extension runs without problems with this Java 2 SDK, provided +that you use Perl >= 5.8.0 or do one of the following: + +1- Rebuild perl and add the libthread library in front of all other + libraries (see the 'BUILDING PERL' section below). You should also + use gcc and your system's malloc (not perl's). + This is the recommended solution. + +2- Add the proper version of libthread.so to your LD_PRELOAD environment + variable. On my test system this was /lib/libthread.so. + This solution seems viable but thread related crashes/hang-ups have + been reported on some systems. If make crashes while running the test + suite, try running it like this: + % for i in `ls t/*.t` ; do perl -Mblib $i; done + + +CYGWIN +------ +Java 2 SDK 1.3.1: + The JNI extension runs without problems. + + PerlInterpreter: + Compiles but stand-alone (non-JNI initiated) Java interpreter locks + up when attempting to load the DLL. This is a known issue with + initialization of the cygwin1.dll from a non-cygwin DLL or EXE, which + has been "broken" since at least Cygwin 1.5.13. + A possible work around might be to build the PerlInterpreter against + a native Win32 Perl distribution with '-mno-cygwin' GCC compile flag, + although this means you would be using native Perl instead of Cygwin + Perl when using PerlInterpreter. + +Java 2 SDK 1.4.2: + The JNI extension runs without problems. + + PerlInterpreter: + The same as Java 2 SDK 1.3.1 applies. + + PerlNatives: + Compiles and runs fine in a JNI-initiated interpreter, but stand-alone + (non-JNI initiated) Java interpreter locks up when attempting to load + the DLL. This is a known issue with initialization of the + cygwin1.dll from a non-cygwin DLL or EXE, which has been "broken" + since at least Cygwin 1.5.13. + +Java 2 SDK 1.5.0: + The same as Java 2 SDK 1.4.2 applies. + + +BUILDING PERL +------------- +Here's how to rebuild Perl (version < 5.8.0) to get the JNI extension to +work properly: + +- Use all the defaults or whatever makes sense, but no threaded Perl + and no interpreter threads, i.e.: + + Build a threading Perl? [n] n + Build Perl for multiplicity? [n] n + +- When asked what libraries to use, type -lthread (Solaris) or -lpthread + (Linux) and tack on whatever the default is at the end, i.e.: + + What libraries to use? -> [-lnsl -lndbm -lgdbm -ldb -ldl -lm -lc -lcrypt + -lutil] -lpthread -lnsl -lndbm -lgdbm -ldb -ldl -lm -lc -lcrypt -lutil + (under Linux it's -lpthread, but for Solaris it's -lthread) + +- Make sure that you do not use perl's own malloc, i.e.: + + Do you wish to attempt to use the malloc that comes with perl5? -> [n] n + + +RUNNING Inline::Java WITH THE JNI EXTENSION +------------------------------------------- +To run Inline::Java with the JNI extension, do one of the following: + + - set the JNI configuration option to 1 + - set the PERL_INLINE_JAVA_JNI environment variable to 1 + +To run the test suite (make test) with the JNI extension you must use the +PERL_INLINE_JAVA_JNI environment variable + + +USING THE 'SHARED_JVM' MODE +--------------------------- +Inline::Java 0.30 introduced a 'SHARED_JVM' mode that allows many clients +to connect to the same Inline::Java Java server. The 'SHARED_JVM' mode is +meant to be used with forking processes such as Apache with mod_perl. The +'SHARED_JVM' mode does NOT work along with the JNI mode. In fact the author +was not able to successfully fork the Java Virtual Machine under any +circumstances. + @@ -0,0 +1,14 @@ +CODE: +- Localize $@ where required +- Finish PerlInterpreter test suite +- Finish PerlHandle stuff and document + +DOCUMENTATION: +- Document new Callback Interface +- Document InlineJavaPerlObject when finished + +TEST: +- Alpha +- Cygwin + + diff --git a/t/01_init.t b/t/01_init.t new file mode 100644 index 0000000..83b0a60 --- /dev/null +++ b/t/01_init.t @@ -0,0 +1,51 @@ +use strict ; +use Test ; + +BEGIN { + $main::cp = $ENV{CLASSPATH} || "<empty>" ; + plan(tests => 1) ; + mkdir('./_Inline_test', 0777) unless -e './_Inline_test' ; +} + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 'DATA' +) ; + +my $ij = $types1::INLINE ; +$ij = $types1::INLINE ; # Stupid warning... +my $jdk = $ij->get_java_config("J2SDK") ; +my $ver = types1->version() ; + +print STDERR "\nPerl version is $]\n" ; +print STDERR "Inline version is $Inline::VERSION\n" ; +print STDERR "Inline::Java version is $Inline::Java::VERSION\n" ; + +print STDERR "J2SDK version is $ver, from $jdk\n" ; +print STDERR "CLASSPATH is $main::cp\n" ; + +if ($ENV{PERL_INLINE_JAVA_EMBEDDED_JNI}){ + print STDERR "Using JNI extension (embedded).\n" ; +} +elsif ($ENV{PERL_INLINE_JAVA_JNI}){ + print STDERR "Using JNI extension.\n" ; +} + +ok(1) ; + + + +__END__ + +__Java__ + +class types1 { + static public String version(){ + return System.getProperty("java.version") ; + } +} + + + diff --git a/t/02_primitives.t b/t/02_primitives.t new file mode 100755 index 0000000..245557d --- /dev/null +++ b/t/02_primitives.t @@ -0,0 +1,252 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 'DATA' +) ; + + +BEGIN { + plan(tests => 102) ; +} + + +my $t = new types2() ; + +{ + my $max = undef ; + my $min = undef ; + + $max = 127 ; + $min = -128 ; + ok($t->_byte(undef) == 1) ; + ok($t->_byte(0) == 1) ; + ok($t->_byte($max - 1) == $max) ; + ok($t->_byte("$min") == $min + 1) ; + eval {$t->_byte($max + 1)} ; ok($@, qr/out of range/) ; + eval {$t->_byte($min - 1)} ; ok($@, qr/out of range/) ; + ok($t->_Byte(undef) == 0) ; + ok($t->_Byte(0) == 0) ; + ok($t->_Byte($max) == $max) ; + ok($t->_Byte("$min") == $min) ; + eval {$t->_Byte($max + 1)} ; ok($@, qr/out of range/) ; + eval {$t->_Byte($min - 1)} ; ok($@, qr/out of range/) ; + + $max = 32767 ; + $min = -32768 ; + ok($t->_short(undef) == 1) ; + ok($t->_short(0) == 1) ; + ok($t->_short($max - 1) == $max) ; + ok($t->_short("$min") == $min + 1) ; + eval {$t->_short($max + 1)} ; ok($@, qr/out of range/) ; + eval {$t->_short($min - 1)} ; ok($@, qr/out of range/) ; + ok($t->_Short(undef) == 0) ; + ok($t->_Short(0) == 0) ; + ok($t->_Short($max) == $max) ; + ok($t->_Short("$min") == $min) ; + eval {$t->_Short($max + 1)} ; ok($@, qr/out of range/) ; + eval {$t->_Short($min - 1)} ; ok($@, qr/out of range/) ; + + $max = 2147483647 ; + $min = -2147483648 ; + ok($t->_int(undef) == 1) ; + ok($t->_int(0) == 1) ; + ok($t->_int($max - 1) == $max) ; + ok($t->_int("$min") == $min + 1) ; + eval {$t->_int($max + 1)} ; ok($@, qr/out of range/) ; + eval {$t->_int($min - 1)} ; ok($@, qr/out of range/) ; + ok($t->_Integer(undef) == 0) ; + ok($t->_Integer(0) == 0) ; + ok($t->_Integer($max) == $max) ; + ok($t->_Integer("$min") == $min) ; + eval {$t->_Integer($max + 1)} ; ok($@, qr/out of range/) ; + eval {$t->_Integer($min - 1)} ; ok($@, qr/out of range/) ; + + $max = 3.4028235e38 ; + $min = -3.4028235e38 ; + ok($t->_float(undef) == 1) ; + ok($t->_float(0) == 1) ; + ok($t->_float($max / 2)) ; + ok($t->_float($min / 2)) ; + ok($t->_float($max - 1)) ; + ok($t->_float("$min")) ; + eval {$t->_float($max + $max)} ; ok($@, qr/out of range/) ; + eval {$t->_float($min + $min)} ; ok($@, qr/out of range/) ; + ok($t->_Float(undef) == 0) ; + ok($t->_Float(0) == 0) ; + ok($t->_Float($max / 2)) ; + ok($t->_Float($min / 2)) ; + # Equality tests for such large floating point number are not always reliable + ok($t->_Float($max)) ; + ok($t->_Float("$min")) ; + eval {$t->_Float($max + $max)} ; ok($@, qr/out of range/) ; + eval {$t->_Float($min + $min)} ; ok($@, qr/out of range/) ; + + # + # Boundary testing for long, double are not predictable enough + # to be reliable. + # + my $val = 123456 ; + ok($t->_long(undef) == 1) ; + ok($t->_long(0) == 1) ; + ok($t->_long($val - 1) == $val) ; + ok($t->_long("-$val") == -$val + 1) ; + ok($t->_Long(undef) == 0) ; + ok($t->_Long(0) == 0) ; + ok($t->_Long($val) == $val) ; + ok($t->_Long("-$val") == -$val) ; + + $val = 123456.789 ; + ok($t->_double(undef) == 1) ; + ok($t->_double(0) == 1) ; + ok($t->_double($val - 1) == $val) ; + ok($t->_double("-$val") == -$val + 1) ; + ok($t->_Double(undef) == 0) ; + ok($t->_Double(0) == 0) ; + ok($t->_Double($val) == $val) ; + ok($t->_Double("-$val") == -$val) ; + + + # Number is forced to Double + ok($t->_Number(undef) == 0) ; + ok($t->_Number(0) == 0) ; + ok($t->_Number($val) == $val) ; + ok($t->_Number("-$val") == -$val) ; + + ok(! $t->_boolean(undef)) ; + ok(! $t->_boolean(0)) ; + ok(! $t->_boolean("")) ; + ok($t->_boolean("true")) ; + ok($t->_boolean(1)) ; + ok(! $t->_Boolean(undef)) ; + ok(! $t->_Boolean(0)) ; + ok(! $t->_Boolean("")) ; + ok($t->_Boolean("true")) ; + ok($t->_Boolean(1)) ; + + ok($t->_char(undef), "\0") ; + ok($t->_char(0), "0") ; + ok($t->_char("1"), '1') ; + eval {$t->_char("10")} ; ok($@, qr/Can't convert/) ; #' + ok($t->_Character(undef), "\0") ; + ok($t->_Character(0), "0") ; + ok($t->_Character("1"), '1') ; + eval {$t->_Character("10")} ; ok($@, qr/Can't convert/) ; #' + + ok($t->_String(undef), undef) ; + ok($t->_String(0), "0") ; + ok($t->_String("string"), 'string') ; + + my $str = "\r\n&&&\r\n\ntre gfd gf$$ b F D&a;t% R f &p;vf\r\r" ; + ok($t->_String($str), $str) ; + + ok($t->_StringBuffer(undef), undef) ; + ok($t->_StringBuffer(0), "0") ; + ok($t->_StringBuffer("stringbuffer"), 'stringbuffer') ; + + # Test if scalars can pass as java.lang.Object. + # They should be converted to strings. + ok($t->_Object(undef), undef) ; + ok($t->_Object(0), "0") ; + ok($t->_Object(666) == 666) ; + ok($t->_Object("object"), 'object') ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + + + +__END__ + +__Java__ + +class types2 { + public types2(){ + } + + public byte _byte(byte b){ + return (byte)(b + (byte)1) ; + } + + public Byte _Byte(Byte b){ + return b ; + } + + public short _short(short s){ + return (short)(s + (short)1) ; + } + + public Short _Short(Short s){ + return s ; + } + + public int _int(int i){ + return i + 1 ; + } + + public Integer _Integer(Integer i){ + return i ; + } + + public long _long(long l){ + return l + 1 ; + } + + public Long _Long(Long l){ + return l ; + } + + public float _float(float f){ + return f + 1 ; + } + + public Float _Float(Float f){ + return f ; + } + + public double _double(double d){ + return d + 1 ; + } + + public Double _Double(Double d){ + return d ; + } + + public Number _Number(Number n){ + return n ; + } + + public boolean _boolean(boolean b){ + return b ; + } + + public Boolean _Boolean(Boolean b){ + return b ; + } + + public char _char(char c){ + return c ; + } + + public Character _Character(Character c){ + return c ; + } + + public String _String(String s){ + return s ; + } + + public StringBuffer _StringBuffer(StringBuffer sb){ + return sb ; + } + + public Object _Object(Object o){ + return o ; + } +} + + diff --git a/t/02_primitives_1_4.t b/t/02_primitives_1_4.t new file mode 100755 index 0000000..4c81f3f --- /dev/null +++ b/t/02_primitives_1_4.t @@ -0,0 +1,72 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test' ; + + +package t02_14 ; +use Inline( + Java => qq | + class t02_14 { + public static boolean got14(){ + try { + Class c = Class.forName("java.lang.CharSequence") ; + } + catch (ClassNotFoundException cnfe){ + return false ; + } + return true ; + } + } + |, + NAME => 't02_14', +) ; + + +package main ; +BEGIN { + my $got14 = t02_14::t02_14->got14() ; + if (! $got14){ + plan(tests => 0) ; + exit(0) ; + } + + plan(tests => 4) ; +} + + + +use Inline( + Java => 'DATA', +) ; + + + +my $t = new types2_1() ; + +{ + ok($t->_CharSequence(undef), undef) ; + ok($t->_CharSequence(0), "0") ; + ok($t->_CharSequence("charsequence"), "charsequence") ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + + + +__END__ + +__Java__ + +class types2_1 { + public types2_1(){ + } + + public CharSequence _CharSequence(CharSequence c){ + return c ; + } +} + + diff --git a/t/03_objects.t b/t/03_objects.t new file mode 100755 index 0000000..d081ecd --- /dev/null +++ b/t/03_objects.t @@ -0,0 +1,157 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', +) ; + + +BEGIN { + plan(tests => 16) ; +} + + +# Create some objects +my $t = new types3() ; + +{ + my $obj1 = new obj13() ; + eval {my $obj2 = new obj23()} ; ok($@, qr/No public constructor/) ; + my $obj11 = new obj113() ; + + ok($t->_obj1(undef), undef) ; + ok($t->_obj1($obj1)->get_data(), "obj1") ; + ok($t->_obj11($obj11)->get_data(), "obj11") ; + ok($t->_obj1($obj11)->get_data(), "obj11") ; + eval {$t->_int($obj1)} ; ok($@, qr/Can't convert (.*) to primitive int/) ; + eval {$t->_obj11($obj1)} ; ok($@, qr/is not a kind of/) ; + + # Inner class + my $in = new obj13::inner_obj13($obj1) ; + ok($in->{data}, "inner") ; + + # Receive an unbound object and send it back + my $unb = $t->get_unbound() ; + ok($t->send_unbound($unb), "al_elem") ; + + # Unexisting method + eval {$t->toto()} ; ok($@, qr/No public method/) ; + + # Method on unbound object + eval {$unb->toto()} ; ok($@, qr/Can't call method/) ; + + # Incompatible prototype, 1 signature + eval {$t->_obj1(5)} ; ok($@, qr/Can't convert/) ; + + # Incompatible prototype, >1 signature + eval {$t->__obj1(5)} ; ok($@, qr/Can't find any signature/) ; + + # Return a scalar hidden in an object. + ok($t->_olong(), 12345) ; + + # Pass a non-Java object, a hash ref. + my $d = {} ; + eval {$t->_Object($d)} ; ok($@, qr/Can't convert/) ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + +__END__ + +__Java__ + +import java.util.* ; + + +class obj13 { + String data = "obj1" ; + + public obj13() { + } + + public String get_data(){ + return data ; + } + + public class inner_obj13 { + public String data = "inner" ; + + public inner_obj13(){ + } + } +} + +class obj113 extends obj13 { + String data = "obj11" ; + + public obj113() { + } + + public String get_data(){ + return data ; + } +} + + +class obj23 { + String data = "obj2" ; + + obj23() { + } + + public String get_data(){ + return data ; + } +} + + +class types3 { + public types3(){ + } + + public int _int(int i){ + return i + 1 ; + } + + public Object _Object(Object o){ + return o ; + } + + public obj13 _obj1(obj13 o){ + return o ; + } + + + public obj13 __obj1(obj13 o, int i){ + return o ; + } + + + public obj13 __obj1(obj13 o){ + return o ; + } + + + public obj113 _obj11(obj113 o){ + return o ; + } + + public ArrayList get_unbound(){ + ArrayList al = new ArrayList() ; + al.add(0, "al_elem") ; + + return al ; + } + + public String send_unbound(ArrayList al){ + return (String)al.get(0) ; + } + + public Object _olong(){ + return new Long("12345") ; + } +} diff --git a/t/04_members.t b/t/04_members.t new file mode 100755 index 0000000..d4fa845 --- /dev/null +++ b/t/04_members.t @@ -0,0 +1,144 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', +) ; + + +BEGIN { + plan(tests => 28) ; +} + + +my $t = new types4() ; + +{ + $t->{_byte} = 123 ; + ok($t->{_byte} == 123) ; + $t->{_Byte} = 123 ; + ok($t->{_Byte} == 123) ; + + $t->{_short} = 123 ; + ok($t->{_short} == 123) ; + $t->{_Short} = 123 ; + ok($t->{_Short} == 123) ; + + $t->{_int} = 123 ; + ok($t->{_int} == 123) ; + $t->{_Integer} = 123 ; + ok($t->{_Integer} == 123) ; + + $t->{_long} = 123 ; + ok($t->{_long} == 123) ; + $t->{_Long} = 123 ; + ok($t->{_Long} == 123) ; + + $t->{_float} = 123.456 ; + ok($t->{_float} == 123.456) ; + $t->{_Float} = 123.456 ; + ok($t->{_Float} == 123.456) ; + + $t->{_double} = 123.456 ; + ok($t->{_double} == 123.456) ; + $t->{_Double} = 123.456 ; + ok($t->{_Double} == 123.456) ; + + $t->{_boolean} = 1 ; + ok($t->{_boolean}) ; + $t->{_Boolean} = 1 ; + ok($t->{_Boolean}) ; + + $t->{_char} = "a" ; + ok($t->{_char}, "a") ; + $t->{_Character} = "a" ; + ok($t->{_Character}, "a") ; + + $t->{_String} = "string" ; + ok($t->{_String}, "string") ; + $t->{_StringBuffer} = "stringbuffer" ; + ok($t->{_StringBuffer}, "stringbuffer") ; + + my $obj1 = new obj14() ; + $t->{_Object} = $obj1 ; + ok($t->{_Object}->get_data(), "obj1") ; + $t->{_Object} = "object" ; + ok($t->{_Object}, "object") ; + + $t->{_Object} = undef ; + ok($t->{_Object}, undef) ; + $t->{_int} = undef ; + ok($t->{_int} == 0) ; + + # Receive an unbound object and try to call a member + my $unb = $t->get_unbound() ; + eval {$unb->{toto} = 1} ; ok($@, qr/Can't set member/) ; + eval {my $a = $unb->{toto}} ; ok($@, qr/Can't get member/) ; + + # Unexisting member + eval {$t->{toto} = 1} ; ok($@, qr/No public member/) ; + eval {my $a = $t->{toto}} ; ok($@, qr/No public member/) ; + + # Incompatible type + eval {$t->{_long} = $obj1} ; ok($@, qr/Can't convert/) ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + +__END__ + +__Java__ + +import java.util.* ; + +class obj14 { + String data = "obj1" ; + + public obj14() { + } + + public String get_data(){ + return data ; + } +} + + +class types4 { + public byte _byte ; + public Byte _Byte ; + public short _short ; + public Short _Short ; + public int _int ; + public Integer _Integer ; + public long _long ; + public Long _Long ; + public float _float ; + public Float _Float ; + public double _double ; + public Double _Double ; + public boolean _boolean ; + public Boolean _Boolean ; + public char _char ; + public Character _Character ; + public String _String ; + public StringBuffer _StringBuffer ; + public Object _Object ; + + public types4(){ + } + + public ArrayList get_unbound(){ + ArrayList al = new ArrayList() ; + al.add(0, "al_elem") ; + + return al ; + } + + public String send_unbound(ArrayList al){ + return (String)al.get(0) ; + } +} + diff --git a/t/05_arrays.t b/t/05_arrays.t new file mode 100755 index 0000000..c3aacf0 --- /dev/null +++ b/t/05_arrays.t @@ -0,0 +1,265 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA' +) ; + +BEGIN { + plan(tests => 55) ; +} + + +my $t = new types5() ; + +{ + ok(++($t->_byte([12, 34, 56])->[0]) == 124) ; + ok(eq_array($t->_Byte([12, 34, 56]), [12, 34, 56])) ; + ok(++($t->_short([12, 34, 56])->[0]) == 124) ; + ok(eq_array($t->_Short([12, 34, 56]), [12, 34, 56])) ; + ok(++($t->_int([12, 34, 56])->[0]) == 124) ; + ok(eq_array($t->_Integer([12, 34, 56]), [12, 34, 56])) ; + ok(++($t->_long([12, 34, 56])->[0]) == 124) ; + ok(eq_array($t->_Long([12, 34, 56]), [12, 34, 56])) ; + ok(++($t->_float([12.34, 5.6, 7])->[0]) == 124.456) ; + ok(eq_array($t->_Float([12.34, 5.6, 7]), [12.34, 5.6, 7])) ; + ok(++($t->_double([12.34, 5.6, 7])->[0]) == 124.456) ; + ok(eq_array($t->_Double([12.34, 5.6, 7]), [12.34, 5.6, 7])) ; + ok($t->_boolean([1, 0, "tree"])->[0]) ; + ok($t->_Boolean([1, 0])->[0]) ; + ok(! $t->_Boolean([1, 0])->[1]) ; + ok($t->_char(['a', 'b', 'c'])->[0], "A") ; + ok(eq_array($t->_Character(['a', 'b', 'c']), ['a', 'b', 'c'], 1)) ; + my $a = $t->_String(["bla", "ble", "bli"]) ; + ok($a->[0], "STRING") ; + $a->[1] = "wazoo" ; + ok($a->[1], "wazoo") ; + ok($t->_StringBuffer(["bla", "ble", "bli"])->[0], "STRINGBUFFER") ; + + ok($t->_Object(undef), undef) ; + $a = $t->_Object([1, "two", $t]) ; + ok($a->[0], "1") ; + ok($a->[1], "two") ; + ok(UNIVERSAL::isa($a->[2], "main::types5")) ; + ok($a->[2]->{data}->[1], "a") ; + $a->[2]->{data} = ["1", "2"] ; + ok($a->[2]->{data}->[1], 2) ; + + $a->[0]++ ; + ok($a->[0], "2") ; + + $a->[1] = "three" ; + ok($a->[1], "three") ; + + $a->[2] = "string" ; + ok($a->[2], "string") ; + + $a->[0] = $t ; + ok(UNIVERSAL::isa($a->[0], "main::types5")) ; + + # Try some multidimensional arrays. + $a = $t->_StringString([ + ["00", "01"], + ["10", "11"] + ]) ; + + # Try some incomplete multidimensional arrays. + $a = $t->_StringString([ + [undef, "01", "02"], + [undef, "11"], + undef, + ]) ; + ok($a->[1]->[0], undef) ; + + + my $b = $a->[1] ; + ok($t->_String($b)->[0], "STRING") ; + + # Arrays of other arrays + $a = $t->_StringString([ + $a->[0], + ]) ; + ok($a->[0]->[2], "02") ; + + # This is one of the things that won't work. + # Try passing an array as an Object. + eval {$t->_o(["a", "b", "c"])} ; ok($@, qr/Can't create Java array/) ; + ok($t->_o(Inline::Java::coerce( + "java.lang.Object", + ["a", "b", "c"], + "[Ljava.lang.String;"))->[0], "a") ; + $t->{o} = Inline::Java::coerce( + "java.lang.Object", + ["a", "b", "c"], + "[Ljava.lang.String;") ; + ok($t->{o}->[0], "a") ; + $t->{o} = $t->{i} ; + ok($t->{o}->[0], "1") ; + + # Mixed types + eval {$t->_int(["3", "3456", "cat"])} ; ok($@, qr/Can't convert/) ; + ok($t->_Object(["3", "3456", "cat"])->[2], 'cat') ; + + # Badly constructed array + eval {$t->_int(["3", [], "cat"])} ; ok($@, qr/Java array contains mixed types/) ; + eval {$t->_StringString([["3"], "string"])} ; ok($@, qr/Java array contains mixed types/) ; + + # Invalid operations on arrays. + eval {@{$b} = ()} ; ok($@, qr/Operation CLEAR/) ; + eval {pop @{$b}} ; ok($@, qr/Operation POP/) ; + eval {shift @{$b}} ; ok($@, qr/Operation SHIFT/) ; + eval {splice(@{$b}, 0, 1)} ; ok($@, qr/Operation SPLICE/) ; + eval {$b->[10] = 5} ; ok($@, qr/out of bounds/) ; + + # Cool stuff on arrays + $a = $t->_byte([12, 34, 56]) ; + ok(scalar(@{$a}), 3) ; + foreach my $e (@{$a}){ + ok($e =~ /^(123|34|56)$/) ; + } + + # Zero length arrays + $a = $t->_Byte([]) ; + ok(scalar(@$a), 0) ; + $a = $t->_StringString([[], []]) ; + ok(scalar(@{$a}), 2) ; + ok(scalar(@{$a->[0]}), 0) ; + ok(scalar(@{$a->[1]}), 0) ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + +sub eq_array { + my $a1 = shift ; + my $a2 = shift ; + my $eq = shift || 0 ; + + if (scalar(@{$a1}) != scalar(@{$a2})){ + return 0 ; + } + + my $ok = 1 ; + for (0..$#{$a1}){ + if ($eq){ + $ok = ($a1->[$_] eq $a2->[$_]) ; + } + else{ + $ok = ($a1->[$_] == $a2->[$_]) ; + } + last unless $ok ; + } + + return $ok ; +} + + +__END__ + +__Java__ + + +class types5 { + public Object o ; + public int i[] = {1, 2, 3} ; + public String data[] = {"d", "a", "t", "a"} ; + public types5(){ + } + + public byte[] _byte(byte b[]){ + b[0] = (byte)123 ; + return b ; + } + + public Byte[] _Byte(Byte b[]){ + return b ; + } + + public short[] _short(short s[]){ + s[0] = (short)123 ; + return s ; + } + + public Short[] _Short(Short s[]){ + return s ; + } + + public int[] _int(int i[]){ + i[0] = 123 ; + return i ; + } + + public Integer[] _Integer(Integer i[]){ + return i ; + } + + public long[] _long(long l[]){ + l[0] = 123 ; + return l ; + } + + public Long[] _Long(Long l[]){ + return l ; + } + + public float[] _float(float f[]){ + f[0] = (float)123.456 ; + return f ; + } + + public Float[] _Float(Float f[]){ + return f ; + } + + public double[] _double(double d[]){ + d[0] = 123.456 ; + return d ; + } + + public Double[] _Double(Double d[]){ + return d ; + } + + public boolean[] _boolean(boolean b[]){ + b[0] = true ; + return b ; + } + + public Boolean[] _Boolean(Boolean b[]){ + return b ; + } + + public char[] _char(char c[]){ + c[0] = 'A' ; + return c ; + } + + public Character[] _Character(Character c[]){ + return c ; + } + + public String[] _String(String s[]){ + s[0] = "STRING" ; + return s ; + } + + public String[][] _StringString(String s[][]){ + return s ; + } + + public StringBuffer[] _StringBuffer(StringBuffer sb[]){ + sb[0] = new StringBuffer("STRINGBUFFER") ; + return sb ; + } + + public Object[] _Object(Object o[]){ + return o ; + } + + public Object _o(Object o){ + return o ; + } +} diff --git a/t/06_static.t b/t/06_static.t new file mode 100755 index 0000000..660d361 --- /dev/null +++ b/t/06_static.t @@ -0,0 +1,84 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', +) ; + + +BEGIN { + plan(tests => 10) ; +} + + +# Methods +ok(p06::types6->get("key"), undef) ; +my $t = new p06::types6("key", "value") ; + +{ + ok($t->get("key"), "value") ; + + # Members + ok($p06::types6::i == 5) ; + $p06::types6::i = 7 ; + ok($t->{i} == 7) ; + + my $t2 = new p06::types6("key2", "value2") ; + my $hm = $p06::types6::hm ; + $p06::types6::hm = undef ; + ok(p06::types6->get($hm, "key2"), "value2") ; + + $p06::types6::hm = $hm ; + ok($t2->get("key2"), "value2") ; + + # Calling an instance method without an object reference + eval {p06::types6->set()} ; ok($@, qr/must be called from an object reference/) ; + + # Put in back like before... + $p06::types6::i = 5 ; + ok($p06::types6::i == 5) ; + my $tt = new p06::types6("key", undef) ; + ok($tt->get("key"), undef) ; +} + +# Since $types::hm was returned to the Perl space, it was registered in the object +# HashMap. +ok($t->__get_private()->{proto}->ObjectCount(), 2) ; + + +__END__ + +__Java__ + + +// package test +package p06 ; + + +import java.util.* ; + + +public class types6 { + public static int i = 5 ; + public static HashMap hm = new HashMap() ; + + public types6(String k, String v){ + hm.put(k, v) ; + } + + public static String get(String k){ + return (String)hm.get(k) ; + } + + public static String get(HashMap h, String k){ + return (String)h.get(k) ; + } + + public String set(){ + return "set" ; + } +} + diff --git a/t/07_polymorph.t b/t/07_polymorph.t new file mode 100644 index 0000000..ed9d0e9 --- /dev/null +++ b/t/07_polymorph.t @@ -0,0 +1,161 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', + STUDY => ['java.util.HashMap', 'java.lang.String'], + AUTOSTUDY => 1, +) ; + +use Inline::Java qw(cast coerce) ; + + +BEGIN { + plan(tests => 24) ; +} + + +my $t = new types7() ; + +{ + my $t1 = new t17() ; + + ok($t->func(5), "int") ; + ok($t->func(coerce("char", 5)), "char") ; + ok($t->func(55), "int") ; + ok($t->func("str"), "string") ; + ok($t->func(coerce("java.lang.StringBuffer", "str")), "stringbuffer") ; + + ok($t->f($t->{hm}), "hashmap") ; + ok($t->f(cast("java.lang.Object", $t->{hm})), "object") ; + + ok($t->f(["a", "b", "c"]), "string[]") ; + + ok($t->f(["12.34", "45.67"]), "double[]") ; + ok($t->f(coerce("java.lang.Object", ['a'], "[Ljava.lang.String;")), "object") ; + + eval {$t->func($t1)} ; ok($@, qr/Can't find any signature/) ; + eval {$t->func(cast("int", $t1))} ; ok($@, qr/Can't cast (.*) to a int/) ; + + my $t2 = new t27() ; + ok($t2->f($t2), "t1") ; + ok($t1->f($t2), "t1") ; + ok($t2->f($t1), "t2") ; + ok($t2->f(cast("t17", $t2)), "t2") ; + + ok($t2->f($t1), "t2") ; + + # Here we should always get the more specific stuff + ok($t2->{i}, 7) ; + ok($t2->{j}, 3.1416) ; + + # So this should fail + eval {$t2->{j} = "string"} ; ok($@, qr/Can't convert/) ; + + # Interfaces + my $al = $t1->get_al() ; + ok(0, $t1->count($al)) ; + + my $hm = new java::util::HashMap() ; + $hm->put('key', 'value') ; + my $a = $hm->entrySet()->toArray() ; + foreach my $e (@{$a}){ + ok(cast('java.util.Map$Entry', $e)->getKey(), 'key') ; + } + + my $str = new java::lang::String('test') ; + ok($str, 'test') ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + +__END__ + +__Java__ + + +import java.util.* ; + +class t17 { + public int i = 5 ; + public String j = "toto" ; + + public t17(){ + } + + public String f(t27 o){ + return "t1" ; + } + + public void n(){ + } + + public ArrayList get_al(){ + return new ArrayList() ; + } + + public int count(Collection c){ + return c.size() ; + } +} + + +class t27 extends t17 { + public int i = 7 ; + public double j = 3.1416 ; + + public t27(){ + } + + public String f(t17 o){ + return "t2" ; + } + + public void n(){ + } +} + + +class types7 { + public HashMap hm = new HashMap() ; + + public types7(){ + } + + public String func(String o){ + return "string" ; + } + + public String func(StringBuffer o){ + return "stringbuffer" ; + } + + public String func(int o){ + return "int" ; + } + + public String func(char o){ + return "char" ; + } + + public String f(HashMap o){ + return "hashmap" ; + } + + public String f(Object o){ + return "object" ; + } + + public String f(String o[]){ + return "string[]" ; + } + + public String f(double o[]){ + return "double[]" ; + } +} + diff --git a/t/08_study.t b/t/08_study.t new file mode 100644 index 0000000..5ff1fc7 --- /dev/null +++ b/t/08_study.t @@ -0,0 +1,117 @@ +package study ; + +use strict ; +use Test ; + + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', +) ; + +# There once was a bug with importing code twice. +use Inline( + Java => 'STUDY', + AUTOSTUDY => 1, + STUDY => ['t.types'], + CLASSPATH => '.', +) ; +use Inline( + Java => 'STUDY', + AUTOSTUDY => 1, + STUDY => ['t.types'], + CLASSPATH => '.', +) ; + + +package toto ; + +use Inline( + Java => 'STUDY', + AUTOSTUDY => 1, + STUDY => ['t.types'], + CLASSPATH => '.', +) ; +use Inline( + Java => 'STUDY', + AUTOSTUDY => 1, + STUDY => ['t.types'], + CLASSPATH => '.', + PACKAGE => 'main', +) ; +package study ; + +use Inline::Java qw(study_classes) ; + + + +BEGIN { + plan(tests => 11) ; +} + +study_classes([ + 't.no_const' +]) ; + +my $t = new study::t::types() ; + +{ + ok($t->func(), "study") ; + ok($t->hm()->get("key"), "value") ; + + my $nc = new study::t::no_const() ; + ok($nc->{i}, 5) ; + + my $a = new study::study::a8() ; + ok($a->{i}, 50) ; + ok($a->truth()) ; + ok($a->sa()->[1], 'titi') ; + ok($a->sb()->[0]->get('toto'), 'titi') ; + ok($a->sb()->[1]->get('error'), undef) ; + + my $toto_t = new toto::t::types() ; + ok(1) ; + my $main_t = new t::types() ; + ok(1) ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + +__DATA__ + +__Java__ + +// Use a public class +package study ; + +import java.util.* ; + +public class a8 { + public int i = 50 ; + + public a8(){ + } + + public boolean truth(){ + return true ; + } + + public String [] sa(){ + String a[] = {"toto", "titi"} ; + return a ; + } + + public HashMap [] sb(){ + HashMap h1 = new HashMap() ; + HashMap h2 = new HashMap() ; + h1.put("toto", "titi") ; + h2.put("tata", "tete") ; + + HashMap a[] = {h1, h2} ; + return a ; + } +} + diff --git a/t/09_usages.t b/t/09_usages.t new file mode 100755 index 0000000..07cfa35 --- /dev/null +++ b/t/09_usages.t @@ -0,0 +1,70 @@ +use strict ; +use Test ; + + +use Inline Config => + DIRECTORY => './_Inline_test'; + + +BEGIN { + plan(tests => 6) ; +} + + + +package t09::p1 ; +use Inline( + Java => qq | + class t09p1 { + public static String name = "p1" ; + + public t09p1(){ + } + + public static String get_prop(int n){ + return System.getProperty("prop" + n) ; + } + } + |, + NAME => 't09::p1', + EXTRA_JAVA_ARGS => '-Dprop1="c:\program files" -Dprop3=42', +) ; + + +package t09::p2 ; +use Inline( + Java => qq | + class t09p2 { + public static String name = "p2" ; + } + |, + NAME => 't09::p2', +) ; + + + +package t09::p3 ; +Inline->bind( + Java => qq | + class t09p3 { + public static String name = "p3" ; + + } + |, + NAME => 't09::p3', +) ; + + +package main ; + +my $t = new t09::p1::t09p1() ; + +{ + ok($t->{name}, "p1") ; + ok($t->get_prop(1), 'c:\program files') ; + ok($t->get_prop(3), 42) ; + ok($t09::p2::t09p2::name . $t09::p3::t09p3::name, "p2p3") ; + ok($t09::p2::t09p2::name . $t09::p3::t09p3::name, "p2p3") ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; diff --git a/t/10_1_shared_alone.t b/t/10_1_shared_alone.t new file mode 100755 index 0000000..49b5124 --- /dev/null +++ b/t/10_1_shared_alone.t @@ -0,0 +1,35 @@ +use strict ; +use Test ; + + +BEGIN { + if ($ENV{PERL_INLINE_JAVA_JNI}){ + plan(tests => 0) ; + exit ; + } + else{ + plan(tests => 4) ; + } +} + + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 't/shared.java', + SHARED_JVM => 1, + PORT => 17891 +) ; + + +my $t = new t10() ; + +{ + ok($t->{i}, 5) ; + ok(! Inline::Java::i_am_JVM_owner()) ; + Inline::Java::capture_JVM() ; + ok(Inline::Java::i_am_JVM_owner()) ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; diff --git a/t/10_5_shared_fork.t b/t/10_5_shared_fork.t new file mode 100755 index 0000000..049f52f --- /dev/null +++ b/t/10_5_shared_fork.t @@ -0,0 +1,72 @@ +package t10 ; + +use strict ; +use Test ; + + +BEGIN { + # Leave previous server enough time to die... + sleep(1) ; + require Inline::Java::Portable ; + if ($ENV{PERL_INLINE_JAVA_JNI}){ + plan(tests => 0) ; + exit ; + } + elsif (! Inline::Java::Portable::portable('GOT_FORK')){ + plan(tests => 0) ; + exit ; + } + else{ + $t10::nb = 5 ; + plan(tests => $t10::nb + 3) ; + } +} + + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 't/shared.java', + SHARED_JVM => 1, + PORT => 17891, + NAME => 't10', +) ; + + +$t10::t10::i = 0 ; + +my $nb = $t10::nb ; +my $sum = (($nb) * ($nb + 1)) / 2 ; +for (my $i = 0 ; $i < $nb ; $i++){ + if (! fork()){ + do_child($i) ; + } +} + + +# Wait for kids to finish +for (my $i = 0 ; $i < $nb ; $i++){ + wait() ; + ok(1) ; +} + +ok($t10::t10::i, $sum) ; + +# Bring down the JVM +ok(! Inline::Java::i_am_JVM_owner()) ; +Inline::Java::capture_JVM() ; +ok(Inline::Java::i_am_JVM_owner()) ; + + +sub do_child { + my $i = shift ; + + Inline::Java::reconnect_JVM() ; + + my $t = new t10::t10() ; + for (my $j = 0 ; $j <= $i ; $j++){ + $t->incr() ; + } + exit ; +} diff --git a/t/10_6_shared_sim.t b/t/10_6_shared_sim.t new file mode 100644 index 0000000..128b865 --- /dev/null +++ b/t/10_6_shared_sim.t @@ -0,0 +1,69 @@ +package t10 ; + +use strict ; +use Test ; + + +use Inline Config => + DIRECTORY => './_Inline_test'; + + +BEGIN { + # Leave previous server enough time to die... + sleep(1) ; + if ($ENV{PERL_INLINE_JAVA_JNI}){ + plan(tests => 0) ; + exit ; + } + else{ + plan(tests => 7) ; + } +} + + + +Inline->bind( + Java => 't/shared.java', + SHARED_JVM => 1, + PORT => 17891, + NAME => 't10', +) ; +{ + my $t = new t10::t10() ; + ok($t->{i}++, 5) ; + ok(! Inline::Java::i_am_JVM_owner()) ; +} +my $JVM1 = Inline::Java::__get_JVM() ; +$JVM1->{destroyed} = 1 ; +Inline::Java::__clear_JVM() ; + + +Inline->bind( + Java => 't/shared.java', + SHARED_JVM => 1, + PORT => 17891, + NAME => 't10', +) ; +{ + my $t = new t10::t10() ; + ok($t->{i}++, 6) ; + ok(! Inline::Java::i_am_JVM_owner()) ; +} +my $JVM2 = Inline::Java::__get_JVM() ; +$JVM2->{destroyed} = 1 ; +Inline::Java::__clear_JVM() ; + + +Inline->bind( + Java => 't/shared.java', + SHARED_JVM => 1, + PORT => 17891, + NAME => 't10', +) ; +{ + my $t = new t10::t10() ; + ok($t->{i}, 7) ; + ok(! Inline::Java::i_am_JVM_owner()) ; + Inline::Java::capture_JVM() ; + ok(Inline::Java::i_am_JVM_owner()) ; +} diff --git a/t/11_exceptions.t b/t/11_exceptions.t new file mode 100755 index 0000000..eb1f750 --- /dev/null +++ b/t/11_exceptions.t @@ -0,0 +1,160 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', +) ; + +use Inline::Java qw(caught) ; + + +BEGIN { + # Leave previous server enough time to die... + sleep(1) ; + plan(tests => 8) ; +} + + +my $t = new t9(0) ; + +{ + my $msg = '' ; + eval { + $t->f() ; + } ; + if ($@){ + if (caught("java.io.IOException")){ + $msg = $@->getMessage() . "io" ; + } + elsif (caught("java.lang.Exception")){ + $msg = $@->getMessage() ; + } + else { + die $@ ; + } + } ; + ok($msg, "from fio") ; + + $msg = '' ; + eval { + $t->f() ; + } ; + if ($@){ + if (caught("java.lang.Throwable")){ + $msg = $@->getMessage() ; + } + elsif (caught("java.io.IOException")){ + $msg = $@->getMessage() . "io" ; + } + else { + die $@ ; + } + } + ok($msg, "from f") ; + + + $msg = '' ; + eval { + die("not e\n") ; + } ; + if ($@){ + if (caught("java.lang.Exception")){ + $msg = $@->getMessage() ; + } + else { + $msg = $@ ; + } + } + ok($msg, "not e\n") ; + + + my $e = $t->f2() ; + ok($e->getMessage(), "from f2") ; + + + $msg = '' ; + eval { + my $t2 = new t9(1) ; + } ; + if ($@){ + if (caught("java.lang.Exception")){ + $msg = $@->getMessage() ; + } + else{ + die $@ ; + } + } + ok($msg, "from const") ; + + # Undeclared exception, java.lang.NullPointerException + $msg = '' ; + eval { + my $t2 = new t9(0) ; + $t2->len(undef) ; + } ; + if ($@){ + if (caught("java.lang.NullPointerException")){ + $msg = "null" ; + } + else { + die $@ ; + } + } + ok($msg, "null") ; + + # Undeclared exception, java.lang.NullPointerException + $msg = '' ; + eval { + my $t2 = new t9(0) ; + $t2->len(undef) ; + } ; + if ($@){ + if (caught("java.lang.IOException")){ + $msg = "io" ; + } + elsif (caught("java.lang.Exception")){ + $msg = "null" ; + } + else{ + die $@ ; + } + } + ok($msg, "null") ; + + # Make sure the last exception is not lying around... + $@ = undef ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + +__END__ + +__Java__ + + +import java.io.* ; + +class t9 { + public t9(boolean t) throws Exception { + if (t){ + throw new Exception("from const") ; + } + } + + public String f() throws IOException { + throw new IOException("from f") ; + } + + public IOException f2() { + return new IOException("from f2") ; + } + + public int len(String s) { + return s.length() ; + } +} + diff --git a/t/12_1_callbacks.t b/t/12_1_callbacks.t new file mode 100755 index 0000000..e479506 --- /dev/null +++ b/t/12_1_callbacks.t @@ -0,0 +1,422 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline ( + Java => 'DATA', + STUDY => ['org.perl.inline.java.InlineJavaPerlCaller'], + STARTUP_DELAY => 20, +) ; + +use Inline::Java qw(caught) ; + + +BEGIN { + my $cnt = 37 ; + plan(tests => $cnt) ; +} + +my $mtc_cnt = 0 ; +my $mtc_mode = 0 ; +my $t = new t15() ; + +{ + eval { + ok($t->add(5, 6), 11) ; + ok($t->add_via_perl(5, 6), 11) ; + my $a = $t->incr_via_perl([7, 6, 5]) ; + ok($a->[1], 7) ; + $a = $t->incr_via_perl_ctx($a) ; + ok($a->[1], 8) ; + ok($t->mul(5, 6), 30) ; + ok($t->mul_via_perl(5, 6), 30) ; + ok($t->silly_mul(3, 2), 6) ; + ok($t->silly_mul_via_perl(3, 2), 6) ; + + ok(add_via_java(3, 4), 7) ; + + ok($t->add_via_perl_via_java(3, 4), 7) ; + ok($t->silly_mul_via_perl_via_java(10, 9), 90) ; + + ok(t15->add_via_perl_via_java_t($t, 6, 9), 15) ; + + ok($t->cat_via_perl("Inline", "Java"), "InlineJava") ; + + ok($t->perl_static(), 'main->static') ; + + ok(twister(20, 0, 0), "return perl twister") ; + ok($t->twister(20, 0, 0), "return java twister") ; + + eval {twister(20, 0, 1)} ; ok($@, qr/^throw perl twister/) ; + + my $msg = '' ; + eval {$t->twister(20, 0, 1)} ; + if ($@) { + if (caught('t15$OwnException')){ + $msg = $@->getMessage() ; + } + else{ + die $@ ; + } + } + ok($msg, "throw java twister") ; + + eval {$t->bug()} ; ok($@, qr/^bug/) ; + + ok($t->perlt()->add(5, 6), 11) ; + + eval {$t->perldummy()} ; ok($@, qr/Can't propagate non-/) ; #' + + $t->mtc_callbacks(20) ; + $t->StartCallbackLoop() ; + ok($mtc_cnt, 20) ; + + $mtc_cnt = -30 ; + $t->mtc_callbacks2(50) ; + $t->StartCallbackLoop() ; + ok($mtc_cnt, 20) ; + + $mtc_cnt = 0 ; + $mtc_mode = 1 ; + $t->mtc_callbacks2(20) ; + $t->StartCallbackLoop() ; + ok($mtc_cnt, 20) ; + + $mtc_cnt = 0 ; + $mtc_mode = 2 ; + $t->mtc_callbacks2(20) ; + $t->OpenCallbackStream() ; + while (($mtc_cnt < 20)&&($t->WaitForCallback(-1) > 0)){ + $t->ProcessNextCallback() ; + } + ok($mtc_cnt, 20) ; + + $mtc_cnt = 0 ; + $mtc_mode = 2 ; + $t->mtc_callbacks2(10) ; + while ($t->WaitForCallback(3.1416) > 0){ + ok($t->WaitForCallback(0) >= 1) ; + $t->ProcessNextCallback() ; + } + ok($mtc_cnt, 10) ; + + # Unfortunately we can't test this because the Thread.run method doesn't allow us + # to throw any exceptions... + # $t->mtc_callbacks_error() ; + } ; + if ($@){ + if (caught("java.lang.Throwable")){ + $@->printStackTrace() ; + die("Caught Java Exception") ; + } + else{ + die $@ ; + } + } +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + +sub add { + my $i = shift ; + my $j = shift ; + + return $i + $j ; +} + + +sub incr { + my $ija = shift ; + + for (my $i = 0 ; $i < $ija->length() ; $i++){ + $ija->[$i]++ ; + } + + return wantarray ? @{$ija} : $ija ; +} + + +sub mul { + my $i = shift ; + my $j = shift ; + + return $i * $j ; +} + + +sub cat { + my $i = shift ; + my $j = shift ; + + return $i . $j ; +} + + +sub add_via_java { + my $i = shift ; + my $j = shift ; + + return $t->add($i, $j) ; +} + + +sub add_via_java_t { + my $_t = shift ; + my $i = shift ; + my $j = shift ; + + return $_t->add($i, $j) ; +} + + +sub twister { + my $max = shift ; + my $cnt = shift ; + my $explode = shift ; + + if ($cnt == $max){ + if ($explode){ + die("throw perl twister") ; + } + else{ + return "return perl twister" ; + } + } + else{ + return $t->twister($max, $cnt+1, $explode) ; + } +} + + +sub t { + return $t ; +} + + +sub dummy { + die(bless({}, "Inline::Java::dummy")) ; +} + + + +sub mt_callback { + my $pc = shift ; + $mtc_cnt++ ; + if ($mtc_cnt >= 20){ + if ($mtc_mode == 0){ + $pc->StopCallbackLoop() ; + } + elsif ($mtc_mode == 1){ + my $o = new org::perl::inline::java::InlineJavaPerlCaller() ; + $o->StopCallbackLoop() ; + } + } +} + + +sub static_method { + my $class = shift ; + + return 'main->static' ; +} + + +__END__ + +__Java__ + + +import java.io.* ; +import org.perl.inline.java.* ; + +class t15 extends InlineJavaPerlCaller { + class OwnException extends Exception { + OwnException(String msg){ + super(msg) ; + } + } + + class OwnThread extends Thread { + InlineJavaPerlCaller pc = null ; + boolean error = false ; + + OwnThread(InlineJavaPerlCaller _pc, int nb, boolean err){ + super("CALLBACK-TEST-THREAD-#" + nb) ; + pc = _pc ; + error = err ; + } + + public void run(){ + try { + if (! error){ + pc.CallPerlSub("main::mt_callback", new Object [] {pc}) ; + } + else { + new InlineJavaPerlCaller() ; + } + } + catch (InlineJavaException ie){ + ie.printStackTrace() ; + } + catch (InlineJavaPerlException ipe){ + ipe.printStackTrace() ; + } + } + } + + public t15() throws InlineJavaException { + } + + public int add(int a, int b){ + return a + b ; + } + + public int mul(int a, int b){ + return a * b ; + } + + public int silly_mul(int a, int b){ + int ret = 0 ; + for (int i = 0 ; i < b ; i++){ + ret = add(ret, a) ; + } + return a * b ; + } + + public int silly_mul_via_perl(int a, int b) throws InlineJavaException, InlineJavaPerlException { + int ret = 0 ; + for (int i = 0 ; i < b ; i++){ + ret = add_via_perl(ret, a) ; + } + return ret ; + } + + public int add_via_perl(int a, int b) throws InlineJavaException, InlineJavaPerlException { + String val = (String)CallPerlSub("main::add", + new Object [] {new Integer(a), new Integer(b)}) ; + + return new Integer(val).intValue() ; + } + + public int [] incr_via_perl(int a[]) throws InlineJavaException, InlineJavaPerlException { + int [] r = (int [])CallPerlSub("main::incr", + new Object [] {a}, a.getClass()) ; + + return r ; + } + + public int [] incr_via_perl_ctx(int a[]) throws InlineJavaException, InlineJavaPerlException { + int [] r = (int [])CallPerlSub("@main::incr", + new Object [] {a}, a.getClass()) ; + + return r ; + } + + public void death_via_perl() throws InlineJavaException, InlineJavaPerlException { + InlineJavaPerlCaller c = new InlineJavaPerlCaller() ; + c.CallPerlSub("main::death", null) ; + } + + public void except() throws InlineJavaException, InlineJavaPerlException { + throw new InlineJavaPerlException("test") ; + } + + public int mul_via_perl(int a, int b) throws InlineJavaException, InlineJavaPerlException { + String val = (String)CallPerlSub("main::mul", + new Object [] {new Integer(a), new Integer(b)}) ; + + return new Integer(val).intValue() ; + } + + public int add_via_perl_via_java(int a, int b) throws InlineJavaException, InlineJavaPerlException { + String val = (String)CallPerlSub("main::add_via_java", + new Object [] {new Integer(a), new Integer(b)}) ; + + return new Integer(val).intValue() ; + } + + static public int add_via_perl_via_java_t(t15 t, int a, int b) throws InlineJavaException, InlineJavaPerlException { + InlineJavaPerlCaller c = new InlineJavaPerlCaller() ; + String val = (String)c.CallPerlSub("main::add_via_java_t", + new Object [] {t, new Integer(a), new Integer(b)}) ; + + return new Integer(val).intValue() ; + } + + + public int silly_mul_via_perl_via_java(int a, int b) throws InlineJavaException, InlineJavaPerlException { + int ret = 0 ; + for (int i = 0 ; i < b ; i++){ + String val = (String)CallPerlSub("add_via_java", + new Object [] {new Integer(ret), new Integer(a)}) ; + ret = new Integer(val).intValue() ; + } + return ret ; + } + + + public String cat_via_perl(String a, String b) throws InlineJavaException, InlineJavaPerlException { + String val = (String)CallPerlSub("cat", + new Object [] {a, b}) ; + + return val ; + } + + public String twister(int max, int cnt, int explode) throws InlineJavaException, InlineJavaPerlException, OwnException { + if (cnt == max){ + if (explode > 0){ + throw new OwnException("throw java twister") ; + } + else{ + return "return java twister" ; + } + } + else{ + return (String)CallPerlSub("twister", + new Object [] {new Integer(max), new Integer(cnt+1), new Integer(explode)}) ; + } + } + + + public void bug() throws InlineJavaException { + throw new InlineJavaException("bug") ; + } + + + public Object perlt() throws InlineJavaException, InlineJavaPerlException, OwnException { + return CallPerlSub("t", null) ; + } + + + public Object perl_static() throws InlineJavaException, InlineJavaPerlException, OwnException { + return CallPerlStaticMethod("main", "static_method", null) ; + } + + + public Object perldummy() throws InlineJavaException, InlineJavaPerlException, OwnException { + return CallPerlSub("dummy", null) ; + } + + public void mtc_callbacks(int n){ + for (int i = 0 ; i < n ; i++){ + OwnThread t = new OwnThread(this, i, false) ; + t.start() ; + } + } + + public void mtc_callbacks2(int n) throws InlineJavaException, InlineJavaPerlException { + for (int i = 0 ; i < n ; i++){ + InlineJavaPerlCaller pc = new InlineJavaPerlCaller() ; + OwnThread t = new OwnThread(pc, i, false) ; + t.start() ; + } + } + + public void mtc_callbacks_error(){ + OwnThread t = new OwnThread(this, 0, true) ; + t.start() ; + } +} diff --git a/t/13_handles.t b/t/13_handles.t new file mode 100755 index 0000000..9abec99 --- /dev/null +++ b/t/13_handles.t @@ -0,0 +1,95 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test'; + +use Inline( + Java => 'DATA', +) ; + +use Inline::Java qw(caught) ; + + +BEGIN { + # Leave previous server enough time to die... + sleep(1) ; + plan(tests => 12) ; +} + + +my $t = new t13() ; + +{ + my $f = File::Spec->catfile("t", "t13.txt") ; + my $o = t13->getWriter($f) ; + my $h = new Inline::Java::Handle($o) ; + for (my $i = 1 ; $i <= 10 ; $i++){ + print $h "$i\n" ; + } + close($h) ; + ok(1) ; + + $o = t13->getReader($f) ; + $h = new Inline::Java::Handle($o) ; + for (my $i = 1 ; $i <= 10 ; $i++){ + my $l = <$h> ; + ok($l, $i) ; + } + ok(! defined(<$h>)) ; +} + + +# It seems that filehandle destruction leaks on certain version +# of Perl. We will change this test to a warning. +if ($t->__get_private()->{proto}->ObjectCount() != 1){ + warn "\nWARNING: Your Perl version ($]) seems to leak tied filehandles. Using\n" . + "Inline::Java::Handle objects will result in memory leaks both in Perl\n" . + "and in Java\n" ; +} + + +__END__ + +__Java__ + + +import java.io.* ; + +class t13 { + public t13(){ + } + + public static Reader getReader(String file) throws FileNotFoundException { + return new FileReader(file) ; + } + + public static Reader getBufferedReader(String file) throws FileNotFoundException { + return new BufferedReader(new FileReader(file)) ; + } + + public static InputStream getInputStream(String file) throws FileNotFoundException { + return new FileInputStream(file) ; + } + + public static InputStream getBufferedInputStream(String file) throws FileNotFoundException { + return new BufferedInputStream(new FileInputStream(file)) ; + } + + public static Writer getWriter(String file) throws IOException { + return new FileWriter(file) ; + } + + public static Writer getBufferedWriter(String file) throws IOException { + return new BufferedWriter(new FileWriter(file)) ; + } + + public static OutputStream getOutputStream(String file) throws FileNotFoundException { + return new FileOutputStream(file) ; + } + + public static OutputStream getBufferedOutputStream(String file) throws FileNotFoundException { + return new BufferedOutputStream(new FileOutputStream(file)) ; + } +} + diff --git a/t/14_encoding.t b/t/14_encoding.t new file mode 100755 index 0000000..5228978 --- /dev/null +++ b/t/14_encoding.t @@ -0,0 +1,70 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 'DATA' +) ; + + +BEGIN { + plan(tests => 9) ; +} + + +my $t = new t14() ; + +{ + ok($t->_String("A"), "A") ; + ok($t->_String("\x{41}"), "A") ; + ok($t->_String("A"), "\x{41}") ; + + # This is E9 (233), which is e acute. Although the byte + # E9 is invalid in UTF-8, the character 233 is valid and + # all should work out. + ok($t->_String("\x{E9}"), "\x{E9}") ; + my $a = $t->toCharArray("\x{E9}") ; + ok(ord($a->[0]) == 233) ; + + # Send a unicode escape sequence. + ok($t->_String("\x{263A}"), "\x{263A}") ; + + # Generate some binary data + my $bin = '' ; + for (my $i = 0; $i < 256 ; $i++) { + my $c = chr($i) ; + $bin .= $c ; + } + ok($t->_String($bin), $bin) ; + + # Mix it up + ok($t->_String("$bin\x{E9}\x{263A}"), "$bin\x{E9}\x{263A}") ; + +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + + + +__END__ + +__Java__ + +class t14 { + public t14(){ + } + + public String _String(String s){ + return s ; + } + + + public char [] toCharArray(String s){ + return s.toCharArray() ; + } +} + + diff --git a/t/15_native_doubles.t b/t/15_native_doubles.t new file mode 100755 index 0000000..0c57454 --- /dev/null +++ b/t/15_native_doubles.t @@ -0,0 +1,44 @@ +use strict ; +use Test ; + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 'DATA', + NATIVE_DOUBLES => 2, +) ; + + +BEGIN { + plan(tests => 3) ; +} + + +my $t = new t15() ; + +{ + # Here it is hard to test for accuracy, but either it works or it doesn't... + ok($t->_Double(0.056200000000000028) > 0.056) ; + ok($t->_Double(0.056200000000000028) < 0.057) ; +} + +ok($t->__get_private()->{proto}->ObjectCount(), 1) ; + + + + +__END__ + +__Java__ + +class t15 { + public t15(){ + } + + public Double _Double(Double d){ + return d ; + } +} + + diff --git a/t/99_end.t b/t/99_end.t new file mode 100644 index 0000000..c081a42 --- /dev/null +++ b/t/99_end.t @@ -0,0 +1,19 @@ +use strict ; +use Test ; + + +BEGIN { + plan(tests => 1) ; +} + + +use Inline Config => + DIRECTORY => './_Inline_test' ; + +use Inline ( + Java => 'STUDY', +) ; + +Inline::Java::capture_JVM() ; +ok(Inline::Java::i_am_JVM_owner()) ; + diff --git a/t/no_const.class b/t/no_const.class Binary files differnew file mode 100644 index 0000000..98ea5ef --- /dev/null +++ b/t/no_const.class diff --git a/t/no_const.java b/t/no_const.java new file mode 100755 index 0000000..08b8c18 --- /dev/null +++ b/t/no_const.java @@ -0,0 +1,7 @@ +package t ; + + +public class no_const { + public int i = 5 ; +} + diff --git a/t/shared.java b/t/shared.java new file mode 100755 index 0000000..2409b40 --- /dev/null +++ b/t/shared.java @@ -0,0 +1,11 @@ +class t10 { + static public int i = 5 ; + + public t10(){ + } + + static synchronized public void incr(){ + i++ ; + } +} + diff --git a/t/types.class b/t/types.class Binary files differnew file mode 100644 index 0000000..918c408 --- /dev/null +++ b/t/types.class diff --git a/t/types.java b/t/types.java new file mode 100755 index 0000000..b13e0b1 --- /dev/null +++ b/t/types.java @@ -0,0 +1,20 @@ +package t ; + +import java.util.* ; + + +public class types { + public types(){ + } + + public String func(){ + return "study" ; + } + + public HashMap hm(){ + HashMap hm = new HashMap() ; + hm.put("key", "value") ; + + return hm ; + } +} |