Files
narya/bin/genreceiver
T
Michael Bayne e54a4d41f4 The great invocation services rethink of 2002! Rearchitected the remote
method invocation services and converted everything to the new style.
Could this be my biggest checkin ever?


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1642 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-08-14 19:08:01 +00:00

355 lines
10 KiB
Perl
Executable File

#!/usr/bin/perl -w
#
# $Id: genreceiver,v 1.2 2002/08/14 19:07:48 mdb Exp $
#
# This script is used to generate invocation receiver marshaller and
# unmarshaller classes based on receiver interface definitions.
use strict;
use Getopt::Long;
use Template;
use File::Basename;
use POSIX qw(strftime);
use MD5;
# use Dumpvalue;
my $usage = "Usage: $0 [--verbose] [--force] [--classpath classpath] " .
"[--sourcedir sourcedir] <sourcedir/foo/bar/FooReceiver.java> [...]\n";
# get our options
my $verbose;
my $force;
my $classpath;
my $sourcedir = ".";
GetOptions("verbose" => \$verbose,
"force" => \$force,
"classpath=s" => \$classpath,
"sourcedir=s" => \$sourcedir,
);
# make sure we have at least one source file argument
die $usage unless (@ARGV);
my $ISVC_CNAME = "com.threerings.presents.client.InvocationReceiver";
# locate a few necessary other files
my $script = basename($0);
my $rootdir = dirname($0); $rootdir =~ s:bin$:.:g;
my $sendtmpl = "$rootdir/lib/sender.tmpl";
my $dcdrtmpl = "$rootdir/lib/decoder.tmpl";
# now process our source files
my %imports;
my $srcfile;
my $spackage;
while ($srcfile = shift) {
# do a quick check to see if the sender file is newer than the
# receiver file in which case we can stop now
my $tpath = $srcfile;
$tpath =~ s/Receiver/Sender/;
$tpath =~ s:/client/:/server/:;
my $dmtime = (stat($tpath))[9];
my $smtime = (stat($srcfile))[9];
if (!$force && (defined $dmtime) && ($dmtime > $smtime)) {
my @foo = split(/\//, $srcfile);
my $rfile = pop @foo;
warn "Skipping ...$rfile as generated files are up to date.\n"
if ($verbose);
next;
}
# convert the source file given on the command line to a class name
open(SRC, "$srcfile") or die "$script: Unable to read '$srcfile': $!\n";
while (<SRC>) {
if (/^package (\S+);/) {
$spackage = $1;
}
}
$srcfile =~ m:/([^/]+)\.java$:;
my $class = "$spackage.$1";
# run javap on the interface class
my $jpcp = (defined $classpath) ? "-classpath $classpath" : "";
my $jpcmd = "javap $jpcp $class";
# clear out the imports table
%imports = ();
my $ifcname;
my @methods;
my $methcount = 1;
my $lmethcount = 0;
open(JPO, "$jpcmd|") or die "$script: Can't invoke '$jpcmd': $!\n";
while (<JPO>) {
if (/^public interface (\S+) extends ([^, ]+)/) {
$ifcname = $1;
$imports{$ifcname} = 1;
my $sname = $2;
# if this interface does not extend InvocationReceiver, we
# don't want to do nothin'
die "$script: $ifcname does not extend " .
"InvocationReceiver\n (extends $sname).\n"
if ($sname ne $ISVC_CNAME);
} elsif (/^ public abstract void (\S+)\(([^\)]*)\)/) {
my $methname = $1;
my $rawargs = $2; $rawargs =~ s:\$:.:g;
my @methargs = split(/, /, $rawargs);
grab_imports(@methargs);
my @trimmed_args = trim_args(@methargs);
# generate our "sender" method name
my $smethname = $methname;
$smethname =~ s:^received:send:;
# push the method onto the list
push @methods, {
"mname" => $methname,
"sname" => $smethname,
"mcode" => to_caps($methname),
"mcodeval" => $methcount++,
"arglist" => gen_arglist(@trimmed_args),
"wrapped_args" => wrap_arglist(0, @trimmed_args),
"unwrapped_args" => unwrap_arglist(@trimmed_args),
};
} elsif (/^Compiled from/ ||
/ACC_SUPER/ ||
/^\s*[{}]\s*$/) {
# skip it
} else {
warn "$script: ?: $_";
}
}
die "$script: Unable to parse primary interface definition.\n"
unless (defined $ifcname);
print "$script: Processing $ifcname...\n";
# extract the package name
my $ifcpkg = $ifcname;
$ifcpkg =~ s/\.[^.]*$//g;
# extract the receiver name
my $svcname = substr($ifcname, length($ifcpkg)+1);
$svcname =~ s/Receiver$//g;
die "$script: Unable to discern receiver name from $ifcname.\n".
"Receiver interface definitions should be named <foo>Receiver.\n"
if ($svcname eq $ifcname);
# assume everything's in the same package to start
my $sendpkg = $ifcpkg;
my $dcdrpkg = $ifcpkg;
# if the interface is in a 'client' package, we'll put the sender in
# the associated 'server' package and leave the decoder in the
# 'client' package
if ($ifcpkg =~ m/\.client$/) {
$sendpkg =~ s/\.client$/.server/g;
}
# create a template processor
my $ttconfig = {
RELATIVE => 1,
ABSOLUTE => 1,
};
my $tt = Template->new($ttconfig);
my $genstamp = strftime("%T %D", localtime);
# now we generate the <foo>Sender class definition
my $sendpath = $sendpkg;
$sendpath =~ s:\.:/:g;
$sendpath = "$sourcedir/$sendpath/$svcname" . "Sender.java";
print "$script: Generating sender:\n" .
" $sendpkg.${svcname}Sender in\n" .
" $sendpath\n" if ($verbose);
# populate a data table with all the data we'll need to fill out our
# sender template file
my %mimports = %imports;
$mimports{"com.threerings.presents.data.ClientObject"} = 1;
$mimports{"com.threerings.presents.server.InvocationSender"} = 1;
$mimports{"$dcdrpkg.${svcname}Decoder"} = 1;
my @mimportsl = sort keys %mimports;
my %mdata = ( "imports" => \@mimportsl,
"name" => $svcname,
"package" => $sendpkg,
"methods" => \@methods,
"genstamp" => $genstamp,
);
# my $dumper = Dumpvalue->new;
# $dumper->dumpValue(\%mdata);
$tt->process($sendtmpl, \%mdata, $sendpath) or
die "$script: Unable to process template file '$sendtmpl': " .
$tt->error() . "\n";
# now we generate the <foo>Decoder class definition
my $dcdrpath = $dcdrpkg;
$dcdrpath =~ s:\.:/:g;
$dcdrpath = "$sourcedir/$dcdrpath/$svcname" . "Decoder.java";
print "$script: Generating decoder:\n" .
" $dcdrpkg.${svcname}Decoder in\n" .
" $dcdrpath\n" if ($verbose);
# populate a data table with all the data we'll need to fill out our
# decoder template file
my %dimports = %imports;
$dimports{"com.threerings.presents.client.InvocationDecoder"} = 1;
my @dimportsl = sort keys %dimports;
my %ddata = ( "imports" => \@dimportsl,
"name" => $svcname,
"receiver_code" => MD5->hexhash($ifcname),
"package" => $dcdrpkg,
"methods" => \@methods,
"genstamp" => $genstamp,
);
# my $dumper = Dumpvalue->new;
# $dumper->dumpValue(\%ddata);
$tt->process($dcdrtmpl, \%ddata, $dcdrpath) or
die "$script: Unable to process template file '$dcdrtmpl': " .
$tt->error() . "\n";
}
#
# Extracts the imports from the supplied list of arguments.
#
sub grab_imports {
my $class;
foreach $class (@_) {
# skip classes in java.lang
next if ($class =~ m/^java\.lang\.[^.]+$/);
# skip primitive types
next unless ($class =~ m/\./);
# trim array delimiters
my $tclass = $class;
$tclass =~ s:[\[\]]::g;
# stuff the class into our import table
$imports{$tclass} = 1;
}
}
#
# Strips the package name from the method argument types.
#
sub trim_args {
my @targs;
my $arg;
foreach $arg (@_) {
$arg =~ s/^.*\.([^.]+)$/$1/;
push @targs, $arg;
}
return @targs;
}
#
# Converts a mixed case name to uppercase with underscores.
#
sub to_caps {
my ($fcode) = @_;
$fcode =~ s/([a-z])([A-Z])/$1_$2/g;
$fcode =~ tr/a-z/A-Z/;
return $fcode;
}
#
# Generates an argument list with argument names from a list of argument
# types.
#
sub gen_arglist {
my $arg;
my $arglist = "";
my $i = 1;
foreach $arg (@_) {
$arglist .= ", " if (length($arglist) > 0);
$arglist .= "$arg arg$i";
$i++;
}
return $arglist;
}
#
# Converts the supplied argument type list into a list of values suitable
# for use in an Object array initializer (meaning object types are used as
# is and primitive types are wrapped).
#
sub wrap_arglist {
my $skip = shift @_;
my $argvals = "";
my $i = 0;
my $arg;
foreach $arg (@_) {
$i++;
next if ($i <= $skip);
$argvals .= ", " if (length($argvals) > 0);
if ($arg eq "boolean") {
$argvals .= "new Boolean(arg$i)";
} elsif ($arg eq "byte") {
$argvals .= "new Byte(arg$i)";
} elsif ($arg eq "short") {
$argvals .= "new Short(arg$i)";
} elsif ($arg eq "char") {
$argvals .= "new Char(arg$i)";
} elsif ($arg eq "int") {
$argvals .= "new Integer(arg$i)";
} elsif ($arg eq "long") {
$argvals .= "new Long(arg$i)";
} elsif ($arg eq "float") {
$argvals .= "new Float(arg$i)";
} elsif ($arg eq "double") {
$argvals .= "new Double(arg$i)";
} else {
$argvals .= "arg$i";
}
}
return $argvals;
}
#
# Converts the supplied argument type list into a list of values which
# represent the unwrapped value of each element.
#
sub unwrap_arglist {
my $argvals = "";
my $i = 0;
my $arg;
foreach $arg (@_) {
$argvals .= ", " if (length($argvals) > 0);
if ($arg eq "boolean") {
$argvals .= "((Boolean)args[$i]).booleanValue()";
} elsif ($arg eq "byte") {
$argvals .= "((Byte)args[$i]).byteValue()";
} elsif ($arg eq "short") {
$argvals .= "((Short)args[$i]).shortValue()";
} elsif ($arg eq "char") {
$argvals .= "((Char)args[$i]).charValue()";
} elsif ($arg eq "int") {
$argvals .= "((Integer)args[$i]).intValue()";
} elsif ($arg eq "long") {
$argvals .= "((Long)args[$i]).longValue()";
} elsif ($arg eq "float") {
$argvals .= "((Float)args[$i]).floatValue()";
} elsif ($arg eq "double") {
$argvals .= "((Double)args[$i]).doubleValue()";
} else {
$argvals .= "($arg)args[$i]";
}
$i++;
}
return $argvals;
}