#!/usr/bin/perl -w # Copyright 1997, O'Reilly & Associate, Inc. # # This package may be copied under the same terms as Perl itself. package JPL::Compile; use Exporter (); @ISA = qw(Exporter); @EXPORT = qw(files file); use strict; warn "You don't have a recent JDK kit your PATH, so this may fail.\n" unless $ENV{PATH} =~ /(java|jdk1.[1-9])/; sub emit; my $PERL = ""; my $LASTCLASS = ""; my $PERLLINE = 0; my $PROTO; my @protos; my $plfile; my $jpfile; my $hfile; my $h_file; my $cfile; my $jfile; my $classfile; my $DEBUG = $ENV{JPLDEBUG}; my %ptype = qw( Z boolean B byte C char S short I int J long F float D double ); $ENV{CLASSPATH} =~ s/^/.:/ unless $ENV{CLASSPATH} =~ /^\.(?::|$)/; unless (caller) { files(@ARGV); } ####################################################################### sub files { foreach my $jpfile (@_) { file($jpfile); } print "make\n"; system "make"; } sub file { my $jpfile = shift; my $JAVA = ""; my $lastpos = 0; my $linenum = 2; my %classseen; my %fieldsig; my %staticfield; (my $file = $jpfile) =~ s/\.jpl$//; $jpfile = "$file.jpl"; $jfile = "$file.java"; $hfile = "$file.h"; $cfile = "$file.c"; $plfile = "$file.pl"; $classfile = "$file.class"; ($h_file = $hfile) =~ s/_/_0005f/g; emit_c_header(); # Extract out arg names from .java file, since .class doesn't have 'em. open(JPFILE, $jpfile) or die "Can't open $jpfile: $!\n"; undef $/; $_ = ; close JPFILE; die "$jpfile doesn't seem to define class $file!\n" unless /class\s+\b$file\b[\w\s.,]*{/; @protos = (); open(JFILE, ">$jfile") or die "Can't create $jfile: $!\n"; while (m/\bperl\b([^\n]*?\b(\w+)\s*\(\s*(.*?)\s*\)[\s\w.,]*)\{\{(.*?)\}\}/sg) { $JAVA = substr($`, $lastpos); $lastpos = pos $_; $JAVA .= "native"; $JAVA .= $1; my $method = $2; my $proto = $3; my $perl = $4; (my $repl = $4) =~ tr/\n//cd; $JAVA .= ';'; $linenum += $JAVA =~ tr/\n/\n/; $JAVA .= $repl; print JFILE $JAVA; $proto =~ s/\s+/ /g; $perl =~ s/^[ \t]+\Z//m; $perl =~ s/^[ \t]*\n//; push(@protos, [$method, $proto, $perl, $linenum]); $linenum += $repl =~ tr/\n/\n/; } print JFILE <<"END"; static { System.loadLibrary("$file"); PerlInterpreter pi = new PerlInterpreter().fetch(); // pi.eval("\$JPL::DEBUG = \$ENV{JPLDEBUG};"); pi.eval("warn qq{loading $file\\n} if \$JPL::DEBUG"); pi.eval("eval {require '$plfile'}; print \$@ if \$@;"); } END print JFILE substr($_, $lastpos); close JFILE; # Produce the corresponding .h file. Should really use make... if (not -s $hfile or -M $hfile > -M $jfile) { if (not -s $classfile or -M $classfile > -M $jfile) { unlink $classfile; print "javac $jfile\n"; system "javac $jfile" and die "Couldn't run javac: exit $?\n"; if (not -s $classfile or -M $classfile > -M $jfile) { die "Couldn't produce $classfile from $jfile!"; } } unlink $hfile; print "javah -jni $file\n"; system "javah -jni $file" and die "Couldn't run javah: exit $?\n"; if (not -s $hfile and -s $h_file) { rename $h_file, $hfile; } if (not -s $hfile or -M $hfile > -M $jfile) { die "Couldn't produce $hfile from $classfile!"; } } # Easiest place to get fields is from javap. print "javap -s $file\n"; open(JP, "javap -s $file|"); $/ = "\n"; while () { if (/^\s+([A-Za-z_].*) (\w+)[\[\d\]]*;/) { my $jtype = $1; my $name = $2; $_ = ; s!^\s*/\*\s*!!; s!\s*\*/\s*!!; print "Field $jtype $name $_\n" if $DEBUG; $fieldsig{$name} = $_; $staticfield{$name} = $jtype =~ /\bstatic\b/; } while (m/L([^;]*);/g) { my $pclass = j2p_class($1); $classseen{$pclass}++; } } close JP; open(HFILE, $hfile) or die "Couldn't open $hfile: $!\n"; undef $/; $_ = ; close HFILE; die "panic: native method mismatch" if @protos != s/^JNIEXPORT/JNIEXPORT/gm; $PROTO = 0; while (m{ \*\s*Class:\s*(\w+)\s* \*\s*Method:\s*(\w+)\s* \*\s*Signature:\s*(\S+)\s*\*/\s* JNIEXPORT\s*(.*?)\s*JNICALL\s*(\w+)\s*\((.*?)\) }gx) { my $class = $1; my $method = $2; my $signature = $3; my $rettype = $4; my $cname = $5; my $ctypes = $6; $class =~ s/_0005f/_/g; if ($method ne $protos[$PROTO][0]) { die "Method name mismatch: $method vs $protos[$PROTO][0]\n"; } print "$class.$method($protos[$PROTO][1]) => $signature $rettype $cname($ctypes)\n" if $DEBUG; # Insert argument names into parameter list. my $env = "env"; my $obj = "obj"; my @jargs = split(/\s*,\s*/, $protos[$PROTO][1]); foreach my $arg (@jargs) { $arg =~ s/^.*\b(\w+).*$/${1}/; } my @tmpargs = @jargs; unshift(@tmpargs, $env, $obj); print "\t@tmpargs\n" if $DEBUG; $ctypes .= ","; $ctypes =~ s/,/' ' . shift(@tmpargs) . '_,'/eg; $ctypes =~ s/,$//; $ctypes =~ s/env_/env/; $ctypes =~ s/obj_/obj/; print "\t$ctypes\n" if $DEBUG; my $jlen = @jargs + 1; (my $mangclass = $class) =~ s/_/_1/g; (my $mangmethod = $method) =~ s/_/_1/g; my $plname = $cname; $plname =~ s/^Java_${mangclass}_${mangmethod}/JPL::${class}::${method}/; $plname =~ s/Ljava_lang_String_2/s/g; # Make glue code for each argument. (my $sig = $signature) =~ s/^\(//; my $decls = ""; my $glue = ""; foreach my $jarg (@jargs) { if ($sig =~ s/^[ZBCSI]//) { $glue .= <<""; ! /* $jarg */ ! PUSHs(sv_2mortal(newSViv(${jarg}_))); ! } elsif ($sig =~ s/^[JFD]//) { $glue .= <<""; ! /* $jarg */ ! PUSHs(sv_2mortal(newSVnv(${jarg}_))); ! } elsif ($sig =~ s#^Ljava/lang/String;##) { $glue .= <<""; ! /* $jarg */ ! tmpjb = (jbyte*)(*env)->GetStringUTFChars(env,${jarg}_,0); ! PUSHs(sv_2mortal(newSVpv((char*)tmpjb,0))); ! (*env)->ReleaseStringUTFChars(env,${jarg}_,tmpjb); ! } elsif ($sig =~ s/^L([^;]*);//) { my $pclass = j2p_class($1); $classseen{$pclass}++; $glue .= <<""; ! /* $jarg */ ! if (!${jarg}_stashhv_) ! ${jarg}_stashhv_ = gv_stashpv("$pclass", TRUE); ! ! PUSHs(sv_bless( ! sv_setref_iv(sv_newmortal(), Nullch, (IV)(void*)${jarg}_), ! ${jarg}_stashhv_)); ! if (jpldebug) ! fprintf(stderr, "Done with $jarg\\n"); ! $decls .= <<""; ! static HV* ${jarg}_stashhv_ = 0; } elsif ($sig =~ s/^\[+([ZBCSIJFD]|L[^;]*;)//) { my $pclass = "jarray"; $classseen{$pclass}++; $glue .= <<""; ! /* $jarg */ ! if (!${jarg}_stashhv_) ! ${jarg}_stashhv_ = gv_stashpv("$pclass", TRUE); ! ! PUSHs(sv_bless( ! sv_setref_iv(sv_newmortal(), Nullch, (IV)(void*)${jarg}_), ! ${jarg}_stashhv_)); ! if (jpldebug) ! fprintf(stderr, "Done with $jarg\\n"); ! $decls .= <<""; ! static HV* ${jarg}_stashhv_ = 0; } else { die "Short signature: $signature\n" if $sig eq ""; die "Unrecognized letter '" . substr($sig, 0, 1) . "' in signature $signature\n"; } } $sig =~ s/^\)// or die "Argument mismatch in signature: $method$signature\n"; my $void = $signature =~ /\)V$/; $decls .= <<"" if $signature =~ m#java/lang/String#; ! jbyte* tmpjb; $decls .= <<"" unless $void; ! SV* retsv; ! $rettype retval; ! ! if (jpldebug) ! fprintf(stderr, "Got to $cname\\n"); ! ENTER; ! SAVETMPS; emit <<""; !JNIEXPORT $rettype JNICALL !$cname($ctypes) !{ ! static SV* methodsv = 0; ! static HV* stashhv = 0; ! dSP; $decls ! PUSHMARK(sp); ! EXTEND(sp,$jlen); ! ! sv_setiv(perl_get_sv("JPL::_env_", 1), (IV)(void*)env); ! jplcurenv = env; ! ! if (jpldebug) ! fprintf(stderr, "env = %lx\\n", (long)$env); ! ! if (!methodsv) ! methodsv = (SV*)perl_get_cv("$plname", TRUE); ! if (!stashhv) ! stashhv = gv_stashpv("JPL::$class", TRUE); ! ! if (jpldebug) ! fprintf(stderr, "blessing obj = %lx\\n", obj); ! PUSHs(sv_bless( ! sv_setref_iv(sv_newmortal(), Nullch, (IV)(void*)obj), ! stashhv)); ! $glue # Finally, call the subroutine. my $mod; $mod = "|G_DISCARD" if $void; if ($void) { emit <<""; ! PUTBACK; ! perl_call_sv(methodsv, G_EVAL|G_KEEPERR|G_DISCARD); ! } else { emit <<""; ! PUTBACK; ! if (perl_call_sv(methodsv, G_EVAL|G_KEEPERR)) ! retsv = *PL_stack_sp--; ! else ! retsv = &PL_sv_undef; ! } emit <<""; ! if (SvTRUE(ERRSV)) { ! jthrowable newExcCls; ! ! (*env)->ExceptionDescribe(env); ! (*env)->ExceptionClear(env); ! ! newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException"); ! if (newExcCls) ! (*env)->ThrowNew(env, newExcCls, SvPV(ERRSV,PL_na)); ! } ! # Fix up the return value, if any. if ($sig =~ s/^V//) { emit <<""; ! return; } elsif ($sig =~ s/^[ZBCSI]//) { emit <<""; ! retval = ($rettype)SvIV(retsv); ! FREETMPS; ! LEAVE; ! return retval; } elsif ($sig =~ s/^[JFD]//) { emit <<""; ! retval = ($rettype)SvNV(retsv); ! FREETMPS; ! LEAVE; ! return retval; } elsif ($sig =~ s#^Ljava/lang/String;##) { emit <<""; ! retval = (*env)->NewStringUTF(env, SvPV(retsv,PL_na)); ! FREETMPS; ! LEAVE; ! return retval; } elsif ($sig =~ s/^L[^;]*;//) { emit <<""; ! if (SvROK(retsv)) { ! SV* rv = (SV*)SvRV(retsv); ! if (SvOBJECT(rv)) ! retval = ($rettype)(void*)SvIV(rv); ! else ! retval = ($rettype)(void*)0; ! } ! else ! retval = ($rettype)(void*)0; ! FREETMPS; ! LEAVE; ! return retval; } elsif ($sig =~ s/^\[([ZBCSIJFD])//) { my $elemtype = $1; my $ptype = "\u$ptype{$elemtype}"; my $ntype = "j$ptype{$elemtype}"; my $in = $elemtype =~ /^[JFD]/ ? "N" : "I"; emit <<""; ! if (SvROK(retsv)) { ! SV* rv = (SV*)SvRV(retsv); ! if (SvOBJECT(rv)) ! retval = ($rettype)(void*)SvIV(rv); ! else if (SvTYPE(rv) == SVt_PVAV) { ! jsize len = av_len((AV*)rv) + 1; ! $ntype* buf = ($ntype*)malloc(len * sizeof($ntype)); ! int i; ! SV** esv; ! ! ${ntype}Array ja = (*env)->New${ptype}Array(env, len); ! for (esv = AvARRAY((AV*)rv), i = 0; i < len; esv++, i++) ! buf[i] = ($ntype)Sv${in}V(*esv); ! (*env)->Set${ptype}ArrayRegion(env, ja, 0, len, buf); ! free((void*)buf); ! retval = ($rettype)ja; ! } ! else ! retval = ($rettype)(void*)0; ! } ! else if (SvPOK(retsv)) { ! jsize len = sv_len(retsv) / sizeof($ntype); ! ! ${ntype}Array ja = (*env)->New${ptype}Array(env, len); ! (*env)->Set${ptype}ArrayRegion(env, ja, 0, len, ($ntype*)SvPV(retsv,PL_na)); ! retval = ($rettype)ja; ! } ! else ! retval = ($rettype)(void*)0; ! FREETMPS; ! LEAVE; ! return retval; } elsif ($sig =~ s!^\[Ljava/lang/String;!!) { emit <<""; ! if (SvROK(retsv)) { ! SV* rv = (SV*)SvRV(retsv); ! if (SvOBJECT(rv)) ! retval = ($rettype)(void*)SvIV(rv); ! else if (SvTYPE(rv) == SVt_PVAV) { ! jsize len = av_len((AV*)rv) + 1; ! int i; ! SV** esv; ! static jclass jcl = 0; ! jarray ja; ! ! if (!jcl) ! jcl = (*env)->FindClass(env, "java/lang/String"); ! ja = (*env)->NewObjectArray(env, len, jcl, 0); ! for (esv = AvARRAY((AV*)rv), i = 0; i < len; esv++, i++) { ! jobject str = (jobject)(*env)->NewStringUTF(env, SvPV(*esv,PL_na)); ! (*env)->SetObjectArrayElement(env, ja, i, str); ! } ! retval = ($rettype)ja; ! } ! else ! retval = ($rettype)(void*)0; ! } ! else ! retval = ($rettype)(void*)0; ! FREETMPS; ! LEAVE; ! return retval; } elsif ($sig =~ s/^(\[+)([ZBCSIJFD]|L[^;]*;)//) { my $arity = length $1; my $elemtype = $2; emit <<""; ! if (SvROK(retsv)) { ! SV* rv = (SV*)SvRV(retsv); ! if (SvOBJECT(rv)) ! retval = ($rettype)(void*)SvIV(rv); ! else if (SvTYPE(rv) == SVt_PVAV) { ! jsize len = av_len((AV*)rv) + 1; ! int i; ! SV** esv; ! static jclass jcl = 0; ! jarray ja; ! ! if (!jcl) ! jcl = (*env)->FindClass(env, "java/lang/Object"); ! ja = (*env)->NewObjectArray(env, len, jcl, 0); ! for (esv = AvARRAY((AV*)rv), i = 0; i < len; esv++, i++) { ! if (SvROK(*esv) && (rv = SvRV(*esv)) && SvOBJECT(rv)) { ! (*env)->SetObjectArrayElement(env, ja, i, ! (jobject)(void*)SvIV(rv)); ! } ! else { ! jobject str = (jobject)(*env)->NewStringUTF(env, ! SvPV(*esv,PL_na)); ! (*env)->SetObjectArrayElement(env, ja, i, str); ! } ! } ! retval = ($rettype)ja; ! } ! else ! retval = ($rettype)(void*)0; ! } ! else ! retval = ($rettype)(void*)0; ! FREETMPS; ! LEAVE; ! return retval; } else { die "No return type: $signature\n" if $sig eq ""; die "Unrecognized return type '" . substr($sig, 0, 1) . "' in signature $signature\n"; } emit <<""; !} ! my $perl = ""; if ($class ne $LASTCLASS) { $LASTCLASS = $class; $perl .= <<""; package JPL::${class}; use JNI; use JPL::AutoLoader; \@ISA = qw(jobject); \$clazz = JNI::FindClass("$file");\n foreach my $field (sort keys %fieldsig) { my $sig = $fieldsig{$field}; my $ptype = $ptype{$sig}; if ($ptype) { $ptype = "\u$ptype"; if ($staticfield{$field}) { $perl .= <<""; \$${field}_FieldID = JNI::GetStaticFieldID(\$clazz, "$field", "$sig"); sub $field (\$;\$) { my \$self = shift; if (\@_) { JNI::SetStatic${ptype}Field(\$clazz, \$${field}_FieldID, \$_[0]); } else { JNI::GetStatic${ptype}Field(\$clazz, \$${field}_FieldID); } }\n } else { $perl .= <<""; \$${field}_FieldID = JNI::GetFieldID(\$clazz, "$field", "$sig"); sub $field (\$;\$) { my \$self = shift; if (\@_) { JNI::Set${ptype}Field(\$self, \$${field}_FieldID, \$_[0]); } else { JNI::Get${ptype}Field(\$self, \$${field}_FieldID); } }\n } } else { my $pltype = $sig; if ($pltype =~ s/^L(.*);/$1/) { $pltype =~ s!/!::!g; } else { $pltype = 'jarray'; } if ($pltype eq "java::lang::String") { if ($staticfield{$field}) { $perl .= <<""; \$${field}_FieldID = JNI::GetStaticFieldID(\$clazz, "$field", "$sig"); sub $field (\$;\$) { my \$self = shift; if (\@_) { JNI::SetStaticObjectField(\$clazz, \$${field}_FieldID, ref \$_[0] ? \$_[0] : JNI::NewStringUTF(\$_[0])); } else { JNI::GetStringUTFChars(JNI::GetStaticObjectField(\$clazz, \$${field}_FieldID)); } }\n } else { $perl .= <<""; \$${field}_FieldID = JNI::GetFieldID(\$clazz, "$field", "$sig"); sub $field (\$;\$) { my \$self = shift; if (\@_) { JNI::SetObjectField(\$self, \$${field}_FieldID, ref \$_[0] ? \$_[0] : JNI::NewStringUTF(\$_[0])); } else { JNI::GetStringUTFChars(JNI::GetObjectField(\$self, \$${field}_FieldID)); } }\n } } else { if ($staticfield{$field}) { $perl .= <<""; \$${field}_FieldID = JNI::GetStaticFieldID(\$clazz, "$field", "$sig"); sub $field (\$;\$) { my \$self = shift; if (\@_) { JNI::SetStaticObjectField(\$clazz, \$${field}_FieldID, \$_[0]); } else { bless JNI::GetStaticObjectField(\$clazz, \$${field}_FieldID), "$pltype"; } }\n } else { $perl .= <<""; \$${field}_FieldID = JNI::GetFieldID(\$clazz, "$field", "$sig"); sub $field (\$;\$) { my \$self = shift; if (\@_) { JNI::SetObjectField(\$self, \$${field}_FieldID, \$_[0]); } else { bless JNI::GetObjectField(\$self, \$${field}_FieldID), "$pltype"; } }\n } } } } } $plname =~ s/^JPL::${class}:://; my $proto = '$' x (@jargs + 1); $perl .= "sub $plname ($proto) {\n"; $perl .= ' my ($self, '; foreach my $jarg (@jargs) { $perl .= "\$$jarg, "; } $perl =~ s/, $/) = \@_;\n/; $perl .= <<"END"; warn "JPL::${class}::$plname(\@_)\\n" if \$JPL::DEBUG; #line $protos[$PROTO][3] "$jpfile" $protos[$PROTO][2]} END $PERLLINE += $perl =~ tr/\n/\n/ + 2; $perl .= <<"END"; #line $PERLLINE "" END $PERLLINE--; $PERL .= $perl; } continue { $PROTO++; print "\n" if $DEBUG; } emit_c_footer(); rename $cfile, "$cfile.old"; rename "$cfile.new", $cfile; open(PLFILE, ">$plfile") or die "Can't create $plfile: $!\n"; print PLFILE "BEGIN { \$JPL::_env_ ||= 1; } # suppress bogus embedding\n\n"; if (%classseen) { my @classes = sort keys %classseen; print PLFILE "use JPL::Class qw(@classes);\n\n"; } print PLFILE $PERL; print PLFILE "1;\n"; close PLFILE; print "perl -c $plfile\n"; system "perl -c $plfile" and die "jpl stopped\n"; } sub emit_c_header { open(CFILE, ">$cfile.new") or die "Can't create $cfile.new: $!\n"; emit <<""; !/* This file is automatically generated. Do not modify! */ ! !#include "$hfile" ! !#include "EXTERN.h" !#include "perl.h" ! !#ifndef EXTERN_C !# ifdef __cplusplus !# define EXTERN_C extern "C" !# else !# define EXTERN_C extern !# endif !#endif ! !extern int jpldebug; !extern JNIEnv* jplcurenv; ! } sub emit_c_footer { close CFILE; } sub emit { my $string = shift; $string =~ s/^!//mg; print CFILE $string; } sub j2p_class { my $jclass = shift; $jclass =~ s#/#::#g; $jclass; }