The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#!/usr/bin/perl
use strict;
my %java_class_info;
my $type_caster = Java::Javap::TypeCast->new();
GetOptions(
'javapopts=s' => \(my $opt_javapopts = ''),
'genwith=s' => \(my $opt_genwith = 'Perl6'),
'genopts=s' => \my $opt_genopts,
'outdir=s' => \(my $opt_outdir = '.'),
'nest!' => \(my $opt_nest = 1),
'recurse!' => \(my $opt_recurse = 1),
'compile!' => \(my $opt_compile = 1),
'set_types=s' => sub {
$type_caster->set_type_casts({});
$type_caster->add_type_casts_from_file( $_[1] );
},
'add_types=s' => sub {
$type_caster->add_type_casts_from_file( $_[1] );
},
'trace=i' => \(my $opt_trace = 1),
'help!' => \&help,
) or exit 1;
my @classes = @ARGV
or die "usage: $0 [options] class [ class ... ]\nUse $0 --help for help\n";
if ($opt_compile) {
# XXX fix to be portable to windows
my $perl6_ver = qx{perl6 --version 2>&1};
if ($perl6_ver) {
$perl6_ver = $1 if $perl6_ver =~ m/(Rakudo Perl.*)/; # tidy
print "perl6: $perl6_ver\n";
}
else {
warn "perl6 not available so --compile disabled\n";
$opt_compile = 0;
}
}
$| = 1 if $opt_trace;
$::RD_WARN = 1;
$::RD_TRACE = 1 if $opt_trace >= 10;
my $parser = Java::Javap::Grammar->new();
my $jenny = Java::Javap::Generator->get_generator(
$opt_genwith,
trace_level => $opt_trace,
split(/\s+/, $opt_genopts||''),
);
my %check_status;
foreach my $class ( @classes ) {
load_java_class_info( $class, $opt_recurse );
}
for my $class (sort keys %java_class_info) {
my $info = $java_class_info{$class};
# ignore clases just vivified for referred_to_by_classes
next unless $info->{tree};
# include the decompiled java for reference
my @epilogue = ("=begin pod\n");
if (my $refs = $info->{referred_to_by_classes}) {
push @epilogue, "=head1 Referenced By\n";
push @epilogue, " $_" for sort @$refs;
push @epilogue, "\n(Among the set of classes processed at the same time.)\n";
}
if (my @decomp_norm = split /\n/, $info->{decomp_norm}) {
push @epilogue, "=head1 Java\n";
push @epilogue, " $_" for @decomp_norm;
push @epilogue, "\n";
}
push @epilogue, "=end pod\n";
my $file_name = generate_output_file( {
class_file => $class,
ast => $info->{tree},
javap_command => $info->{javap_command},
type_caster => $type_caster,
epilogue => join("\n", '', @epilogue),
});
$info->{output_filename} = $file_name;
warn "wrote $file_name - $info->{kind} $class\n" if $opt_trace && $file_name;
if ($opt_trace >= 2) {
warn "\t uses @{ $info->{refers_to_classes} }\n"
if @{ $info->{refers_to_classes} };
warn "\t used by @{ $info->{referred_to_by_classes} }\n"
if @{ $info->{referred_to_by_classes} || [] };
}
}
for my $info (
# compile deepest first as a slight optimization
sort { ($b->{depth}||0) <=> ($a->{depth}||0) } values %java_class_info
) {
next unless $opt_compile;
my $class = $info->{java_class_name};
my $file_name = $info->{output_filename}
or next;
warn "compiling $file_name - $info->{kind} $class\n" if $opt_trace;
(my $pirfile = $file_name) =~ s/\.pm6$/.pir/;
my @perl6cmd = ("perl6", "--target=PIR", "--output=$pirfile", $file_name);
local $ENV{PERL6LIB} = join(":",grep { $_ } $opt_outdir,$ENV{PERL6LIB});
warn "\t @perl6cmd\n" if $opt_trace >= 2;
$check_status{$file_name} = (system(@perl6cmd) == 0);
}
if (%check_status) {
my $passed = 0;
for my $file_name (sort keys %check_status) {
my $ok = $check_status{$file_name};
++$passed if $ok;
printf "%7s: %s\n", ($ok ? "ok" : "FAILED"), $file_name
if !$ok or $opt_trace >= 2;
}
printf "%d ok, %d failed.\n", $passed, keys(%check_status)-$passed
if keys %check_status > 1;
}
exit 0;
sub load_java_class_info {
my ($class, $recurse) = @_;
return undef if $java_class_info{$class}->{decomp_norm};
my @javapopts = split / /, $opt_javapopts; # XXX hack
my $javap_command = "javap @javapopts $class";
warn sprintf "loading %s%s%s\n",
$recurse ? (". " x ($recurse-1)) : "",
$class, " @javapopts"
if $opt_trace;
my $decomp_norm = Java::Javap->javap($class, \@javapopts);
warn $decomp_norm if $opt_trace >= 3;
my $decomp_verb = Java::Javap->javap($class, [ @javapopts, '-verbose' ]);
warn $decomp_verb if $opt_trace >= 9;
my $tree = $parser->comp_unit( $decomp_verb )
or die "Error parsing output of '$javap_command'\n";
# tell them which types are used by this class
my $referenced_classes = Java::Javap->get_included_types( $tree, $type_caster );
$java_class_info{$class} = {
%{ $java_class_info{$class} || {} },
java_class_name => $class,
javap_command => $javap_command,
decomp_norm => $decomp_norm,
decomp_verb => $decomp_verb,
tree => $tree,
refers_to_classes => $referenced_classes,
kind => $tree->{class_or_interface},
depth => $recurse,
};
warn "$class: ".Dumper($java_class_info{$class}) if $opt_trace >= 9;
push @{$java_class_info{$_}->{referred_to_by_classes}}, $class
for @$referenced_classes;
if ($recurse) {
for my $ref_class (sort @$referenced_classes) {
if ($ref_class =~ m/\$/) {
warn "$ref_class: skipped private class\n"
if $opt_trace >= 2;
next;
}
load_java_class_info( $ref_class, $recurse+1 );
}
}
return $referenced_classes;
}
sub generate_output_file {
my $params = shift;
my $epilogue = delete $params->{epilogue};
my $output = $jenny->generate( $params );
$output .= $epilogue if $epilogue;
my $file_name;
if ( $opt_outdir eq 'STDOUT' ) {
print $output;
}
else { # put it in a directory
my $class = $params->{class_file};
$file_name = output_filename_for_class($class, 1);
open my $API_MODULE, '>', $file_name
or die "Couldn't write to $file_name: $!\n";
print $API_MODULE $output;
close $API_MODULE or die "Error writing $file_name: $!\n";
}
return $file_name;
}
sub output_filename_for_class {
my ($class, $mkdir) = @_;
my @subdirs = split /\./, $class;
my $class_name = pop @subdirs;
my $module_dir = $opt_outdir;
mkdir $module_dir or -d $module_dir
or die "Can't mkdir $module_dir: $!\n";
if ( $opt_nest ) {
foreach my $subdir ( @subdirs ) {
$module_dir = File::Spec->catdir( $module_dir, $subdir );
mkdir $module_dir or -d $module_dir
or die "Can't mkdir $module_dir: $!\n";
}
}
return File::Spec->catfile( $module_dir, "$class_name.pm6" );
}
sub help {
print <<'EO_Help';
java2perl6api [options] class_file [class_file...]
where options are:
--help this message
--outdir=D root directory for output files (default '.')
--nonest don't place output files in nested directories
--nocompile don't compile the generated modules into .pir files
--norecurse don't recurse into types used by this class
--set_types=S replace typemapings with those in file S
--add_types=S add/override typemapings with those in file S
--javapopts=S a string of command line flags for javap, example:
-j '-classpath /some/path'
--genwith=S the name of a Java::Javap::Generator:: module
which will make the output, defaults to Perl6
--genopts=S strings to pass to your --genwith constructor
--trace=N defines the trace level (integer value), where:
0 means silence, no trace,
1 is the default minimum trace messages,
>= 9 for full trace
EO_Help
exit;
}
__END__
=head1 NAME
java2perl6api - a Java to Perl 6 API translator
=head1 SYNOPSIS
java2perl6api [options] class_file [...]
--help this message
--outdir=D root directory for output files (default '.')
--nonest don't place output files in nested directories
--nocompile don't compile the generated modules into .pir files
--norecurse don't recurse into types used by this class
--set_types=S replace typemapings with those in file S
--add_types=S add/override typemapings with those in file S
--javapopts=S a string of command line flags for javap, example:
-j '-classpath /some/path'
--genwith=S the name of a Java::Javap::Generator:: module
which will make the output, defaults to Perl6
--genopts=S strings to pass to your --genwith constructor
--trace=N defines the trace level (integer value), where:
0 means silence, no trace,
1 is the default minimum trace messages,
>= 9 for full trace
=head1 DESCRIPTION
This script is the driver for the C<Java::Javap> module which reads compiled
Java files, parses them into a tree, and generates output in Perl 6.
=head1 EXAMPLES
To get a single Perl module in the java/sql subdirectory of the current
directory with an API translation of java.sql.Connection:
java2perl6api java.sql.Connection
To get a single Perl module in the current directory with an API for
com.example.YourModule whose class file is in /usr/lib/yourjavas:
java2perl6api --javapopts '-classpath /usr/lib/yourjavas' com.example.YourModule
To put the output from the previous example into the /usr/local/javaapis
directory:
java2perl6api --javapopts '-classpath /usr/lib/yourjavas' \
--outdir /usr/local/javaapis com.example.YourModule
=head1 OPTIONS
=over 4
=item --help
Prints a short help message (the same as the L<SYNOPSIS> above).
=item --javapopts=S
This option takes a single value, you need to quote it. That value
is passed directly to C<javap>. C<-classpath /some/path> is the most
common value. Be aware that C<Java::Javap> parses the output of
C<javap> with a grammar, so some C<javap> flags will cause fatal errors.
=item --outdir=S
Sets the root directory for output files. Default '.' (current directory).
=item --nonest
By default output files are written into a directory tree (rooted at --outdir)
based on the namespace of the Java package.
Use the --nonest to write all files into the root output directory.
=item --norecurse
Disable the default behaviour of recursively processing any classes referenced
by the specified class.
=item --nocompile
Disable compiling the generated Perl 6 .pm6 file into a .pir file.
(Generating a .pir file takes time but significantly speeds up any future use
of the module.)
=item --trace N
Integer value. Assign zero to it (--trace=0) to silence all messages from
the script. 1 is the minimum trace level. An integer >= 9 will emit full
trace messages.
=item --genwith S
Specify the Java::Javap::Generator::* class to use to generate the output files.
Defaults to Perl6 (i.e. C<Java::Javap::Generator::Perl6>) which generates Perl
6 roles in C<.pm6> files.
=back
=head1 AUTHOR
Phil Crow E<lt>crow.phil@gmail.comE<gt> wrote the original verion,
further devalopment by Tim Bunce and others since.
=head1 COPYRIGHT and LICENSE
Copyright (c) 2007, Phil Crow
Copyright (c) 2010, Tim Bunce
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.6 or,
at your option, any later version of Perl 5 you may have available.
=cut
vim: ts=8:sw=4:et