diff --git a/bin/genreceiver b/bin/genreceiver new file mode 100755 index 000000000..e2e97e32d --- /dev/null +++ b/bin/genreceiver @@ -0,0 +1,348 @@ +#!/usr/bin/perl -w +# +# $Id: genreceiver,v 1.1 2002/08/11 05:53:13 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 Dumpvalue; + +my $usage = "Usage: $0 [--verbose] [--force] [--classpath classpath] " . +"[--sourcedir sourcedir] [...]\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 () { + 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 () { + if (/^public interface (\S+) extends (\S+)/) { + $ifcname = $1; + $imports{$ifcname} = 1; + my $sname = $2; + + # if this interface does not extend InvocationReceiver, we + # don't want to do nothin' + die "$script: Receiver interface 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 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 + }; + my $tt = Template->new($ttconfig); + my $genstamp = strftime("%T %D", localtime); + + # now we generate the 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 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_id" => 0, # TODO: do something here + "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/\./); + + # stuff the class into our import table + $imports{$class} = 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; +} diff --git a/lib/decoder.tmpl b/lib/decoder.tmpl new file mode 100644 index 000000000..30b201b54 --- /dev/null +++ b/lib/decoder.tmpl @@ -0,0 +1,57 @@ +// +// $Id: decoder.tmpl,v 1.1 2002/08/11 05:53:13 mdb Exp $ + +package [% package %]; + +[% FOREACH import = imports -%] +import [% import %]; +[% END -%] + +/** + * Dispatches calls to a {@link [% name %]Receiver} instance. + */ +public class [% name %]Decoder extends InvocationDecoder +{ + /** The id used to identify this notification receiver. */ + public static final short RECEIVER_ID = [% receiver_id %]; + +[% FOREACH method = methods -%] + /** The method id used to dispatch {@link [% name %]Receiver#[% method.mname %]} + * notifications. */ + public static final int [% method.mcode %] = [% method.mcodeval %]; + +[% END -%] + /** + * Creates a decoder that may be registered to dispatch invocation + * service notifications to the specified receiver. + */ + public [% name %]Decoder ([% name %]Receiver receiver) + { + this.receiver = receiver; + } + + // documentation inherited + public int getReceiverId () + { + return RECEIVER_ID; + } + + // documentation inherited + public void dispatchNotification (int methodId, Object[] args) + { + switch (methodId) { +[% FOREACH method = methods -%] + case [% method.mcode %]: + (([% name %]Receiver)receiver).[% method.mname %]( + [% method.unwrapped_args %] + ); + return; + +[% END -%] + default: + super.dispatchNotification(methodId, args); + } + } + + // Generated on [% genstamp %]. +} diff --git a/lib/sender.tmpl b/lib/sender.tmpl new file mode 100644 index 000000000..484215641 --- /dev/null +++ b/lib/sender.tmpl @@ -0,0 +1,31 @@ +// +// $Id: sender.tmpl,v 1.1 2002/08/11 05:53:13 mdb Exp $ + +package [% package %]; + +[% FOREACH import = imports -%] +import [% import %]; +[% END -%] + +/** + * Used to issue notifications to a {@link [% name %]Receiver} instance on a + * client. + */ +public class [% name %]Sender extends InvocationSender +{ +[% FOREACH m = methods -%] + /** + * Issues a notification that will result in a call to {@link + * [% name %]Receiver#[% m.mname %]} on a client. + */ + public static void [% m.sname %] ( + ClientObject target, [% m.arglist %]) + { + sendNotification( + target, [% name %]Decoder.RECEIVER_ID, [% name %]Decoder.[% m.mcode %], + new Object[] { [% m.wrapped_args %] }); + } + +[% END -%] + // Generated on [% genstamp %]. +}