Files
narya/bin/genreceiver
T
Andrzej Kapolka 9bbd46fa7b Added isValid() method to Card, ability to deal single hands to CardGameManager.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3135 542714f4-19e9-0310-aa3c-eee0fc999fb1
2004-10-13 19:29:12 +00:00

376 lines
11 KiB
Perl
Executable File

#!/usr/bin/perl -w
#
# $Id: genreceiver,v 1.4 2004/10/13 19:29:12 andrzej 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 = "/usr/local/j2sdk1.4.1/bin/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 = ( "idstr" => slurp_idstr($sendpath),
"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 = ( "idstr" => slurp_idstr($dcdrpath),
"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;
}
#
# If the specified file exists, it is opened and its RCS Id string is read
# and returned.
#
sub slurp_idstr {
my ($path) = @_;
my $idstr = "\$Id\$";
if (-f $path && open(IN, "$path")) {
while (<IN>) {
if (/(\$[I][d]: .*\$)/) {
$idstr = $1;
last;
}
}
close(IN);
}
return $idstr;
}