#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use File::Spec; use Data::Dumper; use Java::Javap; use Java::Javap::Grammar; use Java::Javap::Generator; use Java::Javap::TypeCast; 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 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 above). =item --javapopts=S This option takes a single value, you need to quote it. That value is passed directly to C. C<-classpath /some/path> is the most common value. Be aware that C parses the output of C with a grammar, so some C 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) which generates Perl 6 roles in C<.pm6> files. =back =head1 AUTHOR Phil Crow Ecrow.phil@gmail.comE 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