#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use File::Spec; use Java::Javap; use Java::Javap::Grammar; use Java::Javap::Generator; my $genwith = 'Std'; my $genoptstr = ''; my $jpcmd = ''; my $nest; my $outdir = '.'; my $recurse; my $quiet = 0; my $verbose = 0; #use Parse::RecDescent; $::RD_TRACE = 1; # not yet done: # astas - no, make this a Genterator, # but do write the backends for YAML and Perl GetOptions( 'jpcmd|j=s' => \$jpcmd, 'genwith|g=s' => \$genwith, 'genopts|p=s' => \$genoptstr, 'nest|n' => \$nest, 'outdir|d=s' => \$outdir, 'recurse|r' => \$recurse, 'quiet|q' => \$quiet, 'verbose|v' => \$verbose, 'help|h' => \&help, ); my @genopts = split /\s+/, $genoptstr; my @classes = @ARGV or die "usage: $0 [options] class [ class ... ]\n"; my $parser = Java::Javap::Grammar->new(); my $jenny = Java::Javap::Generator->get_generator( $genwith, @genopts ); my %seen = map { $_ => 1 } @classes; my %done; my $we_did_something = 1; while ( $we_did_something ) { $we_did_something = 0; CLASS: foreach my $class ( sort keys %seen ) { next CLASS if $done{ $class }; $we_did_something++; my $included_classes = eval { process_one_file( $class ) } || []; warn $@ if $@; $done{ $class } = 1; if ($recurse) { foreach my $included_class ( @{ $included_classes } ) { $seen{ $included_class } ||= 1; } } } } sub process_one_file { my $class = shift; my $cmd = "javap $jpcmd $class"; warn "$cmd\n" if $verbose; my $decomp = `javap $jpcmd $class`; my $tree = $parser->comp_unit( $decomp ) or die "Error parsing output of '$cmd'\n"; my $output = $jenny->generate( { class_file => $class, ast => $tree, javap_flags => $jpcmd, } ); if ( $outdir eq 'STDOUT' ) { print $output; } else { # put it in a directory my $module_dir = $outdir; my @subdirs = split /\./, $class; my $class_name = pop @subdirs; mkdir $module_dir or -d $module_dir or die "Can't mkdir $module_dir: $!\n"; my $file; if ( $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"; } } my $file_name = File::Spec->catfile( $module_dir, "$class_name.pm" ); open my $API_MODULE, '>', $file_name or die "Couldn't write to $file_name: $!\n"; print $API_MODULE $output; close $API_MODULE; warn "$class: $file_name\n" unless $quiet; } # tell them which types we saw return Java::Javap->get_included_types( $tree ); } sub help { print <<'EO_Help'; java2perl6 [options] class_file [class_file...] where options are: --jpcmd or -j a string of command line flags for javap, example: -j '-classpath /some/path' --genwith or -g the name of a Java::Javap::Generator:: module which will make the output, defaults to Std --genopts or -p strings to pass to your -g constructor --nest or -n flag indicates output should go in nested directories --outdir or -d top level directory for output --help or -h this message EO_Help exit; } =head1 NAME java2perl6 - a Java to Perl 6 API translator =head1 SYNOPSIS java2perl6 [options] class_file where options are: --jpcmd or -j a string of command line flags for javap, example: -j '-classpath /some/path' --genwith or -g the name of a Java::Javap::Generator:: module which will make the output, defaults to Std --genopts or -p strings to pass to your -g constructor --nest or -n flag indicates output should go in nested directories --outdir or -d top level directory for output --verbose or -v sends chatter to the screen --help or -h this message =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. Note that output could be in other forms if you write a generator, see C for instructions on how to write one and use the C<-g> command line flag once yours is installed. This module ships with only one generator: C. If you write your own generator, you could have it do anything you like including outputing APIs in Perl 5, Python, etc. I plan to add generators for tree dumps using Data::Dumper and YAML. =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: java2perl6 -n 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: java2perl6 -j '-classpath /usr/lib/yourjavas' com.example.YourModule To put the output from the previous example into the /usr/local/javaapis directory: java2perl6 -j '-classpath /usr/lib/yourjavas' \ -d /usr/local/javaapis com.example.YourModule To choose your own generator called C for the first example: java2perl6 -n -g MyGen java.sql.Connection =head1 OPTIONS =over 4 =item --help or -h Prints a short help message (the same as the L above). =item --jpcmd or -j 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 --genwith or -g C uses a factory approach to generation. Basically, any module in the Java::Javap::Generator:: namespace can be used here. Give the module's short name, so to get the default behavior explicitly type: java2perl6 -g Std ... which will load and use C to make the output. See C for the API your module must respond to. =item --genopts or -p Not yet useful. Any value given to this option will be passed to the generator's constructor. These can obviously only be strings. The Std generator does not support any options, but future genertors may. =item --outdir or -d By default, all output is written in the current directory (or one of its subdirectories, if you use C<--nest>). Use this flag to specify an alternate top level directory in which to place output. It may be relative to the current directory or absolute. Remember that the invoking user will need write permission on the directory. =item --nest or -n By default all output is generated at the top level of the C<--outdir>. Use this flag to make the normal subdirectories based on the namespace of the Java package. Thus, since com.example.Module will be called com::example::Module, you probably want to use this flag so the output will go into the com/example subdirectory of the output directory. =item --verbose or -v Turn this on to get chatter about what the script is doing. Currently it is a boolean flag. =back =head1 AUTHOR Phil Crow Ecrow.phil@gmail.comE =head1 COPYRIGHT and LICENSE Copyright (c) 2007, Phil Crow 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