#!/usr/bin/perl -w # # $Id: genreceiver,v 1.3 2002/08/20 19:36:43 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] [...]\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 ([^, ]+)/) { $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 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 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 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 () { if (/(\$[I][d]: .*\$)/) { $idstr = $1; last; } } close(IN); } return $idstr; }