diff --git a/bin/genreceiver b/bin/genreceiver deleted file mode 100755 index 0874d7772..000000000 --- a/bin/genreceiver +++ /dev/null @@ -1,375 +0,0 @@ -#!/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] [...]\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 = "/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 () { - 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; -} diff --git a/build.xml b/build.xml index 149252d03..ade708037 100644 --- a/build.xml +++ b/build.xml @@ -71,17 +71,25 @@ - - - - - - - + + + + + + + + + + + + - + diff --git a/src/java/com/threerings/presents/tools/GenReceiverTask.java b/src/java/com/threerings/presents/tools/GenReceiverTask.java new file mode 100644 index 000000000..189b590b3 --- /dev/null +++ b/src/java/com/threerings/presents/tools/GenReceiverTask.java @@ -0,0 +1,178 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.tools; + +import java.io.File; +import java.io.StringWriter; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.apache.velocity.VelocityContext; + +import com.samskivert.util.CollectionUtil; +import com.samskivert.util.SortableArrayList; +import com.samskivert.util.StringUtil; + +import com.threerings.presents.client.InvocationDecoder; +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.server.InvocationSender; + +/** + * An Ant task for generating invocation receiver marshalling and unmarshalling + * classes. + */ +public class GenReceiverTask extends InvocationTask +{ + // documentation inherited + protected void processService (File source, Class receiver) + { + System.out.println("Processing " + receiver.getName() + "..."); + String rname = receiver.getName(); + String rpackage = ""; + int didx = rname.lastIndexOf("."); + if (didx != -1) { + rpackage = rname.substring(0, didx); + rname = rname.substring(didx+1); + } + + // verify that the receiver class name is as we expect it to be + if (!rname.endsWith("Receiver")) { + System.err.println("Cannot process '" + rname + "':"); + System.err.println( + "Receiver classes must be named SomethingReceiver."); + return; + } + + HashMap imports = new HashMap(); + SortableArrayList methods = new SortableArrayList(); + + // we need to import the receiver itself + imports.put(importify(receiver.getName()), Boolean.TRUE); + + // look through and locate our receiver methods + Method[] methdecls = receiver.getDeclaredMethods(); + for (int ii = 0; ii < methdecls.length; ii++) { + Method m = methdecls[ii]; + // receiver interface methods must be public and abstract + if (!Modifier.isPublic(m.getModifiers()) && + !Modifier.isAbstract(m.getModifiers())) { + continue; + } + methods.add(new ServiceMethod(receiver, m, imports)); + } + methods.sort(); + + generateSender(source, rname, rpackage, methods, + imports.keySet().iterator()); + generateDecoder(source, rname, rpackage, methods, + imports.keySet().iterator()); + } + + protected void generateSender (File source, String rname, String rpackage, + List methods, Iterator imports) + { + String name = StringUtil.replace(rname, "Receiver", ""); + String sname = StringUtil.replace(rname, "Receiver", "Sender"); + String spackage = StringUtil.replace(rpackage, ".client", ".server"); + + // construct our imports list + SortableArrayList implist = new SortableArrayList(); + CollectionUtil.addAll(implist, imports); + checkedAdd(implist, ClientObject.class.getName()); + checkedAdd(implist, InvocationSender.class.getName()); + String dname = StringUtil.replace(rname, "Receiver", "Decoder"); + checkedAdd(implist, rpackage + "." + dname); + implist.sort(); + + VelocityContext ctx = new VelocityContext(); + ctx.put("name", name); + ctx.put("package", spackage); + ctx.put("methods", methods); + ctx.put("imports", implist); + + try { + StringWriter sw = new StringWriter(); + _velocity.mergeTemplate(SENDER_TMPL, "UTF-8", ctx, sw); + + // determine the path to our sender file + String mpath = source.getPath(); + mpath = StringUtil.replace(mpath, "Receiver", "Sender"); + mpath = StringUtil.replace(mpath, "/client/", "/server/"); + + writeFile(mpath, sw.toString()); + + } catch (Exception e) { + System.err.println("Failed processing template"); + e.printStackTrace(System.err); + } + } + + protected void generateDecoder ( + File source, String rname, String rpackage, List methods, + Iterator imports) + { + String name = StringUtil.replace(rname, "Receiver", ""); + String dname = StringUtil.replace(rname, "Receiver", "Decoder"); + + // construct our imports list + SortableArrayList implist = new SortableArrayList(); + CollectionUtil.addAll(implist, imports); + checkedAdd(implist, InvocationDecoder.class.getName()); + implist.sort(); + + VelocityContext ctx = new VelocityContext(); + ctx.put("name", name); + ctx.put("receiver_code", StringUtil.md5hex(rpackage + "." + rname)); + ctx.put("package", rpackage); + ctx.put("methods", methods); + ctx.put("imports", implist); + + try { + StringWriter sw = new StringWriter(); + _velocity.mergeTemplate(DECODER_TMPL, "UTF-8", ctx, sw); + + // determine the path to our sender file + String mpath = source.getPath(); + mpath = StringUtil.replace(mpath, "Receiver", "Decoder"); + + writeFile(mpath, sw.toString()); + + } catch (Exception e) { + System.err.println("Failed processing template"); + e.printStackTrace(System.err); + } + } + + /** Specifies the path to the sender template. */ + protected static final String SENDER_TMPL = + "com/threerings/presents/tools/sender.tmpl"; + + /** Specifies the path to the decoder template. */ + protected static final String DECODER_TMPL = + "com/threerings/presents/tools/decoder.tmpl"; +} diff --git a/src/java/com/threerings/presents/tools/InvocationTask.java b/src/java/com/threerings/presents/tools/InvocationTask.java index 1ced66bb7..dbddba38c 100644 --- a/src/java/com/threerings/presents/tools/InvocationTask.java +++ b/src/java/com/threerings/presents/tools/InvocationTask.java @@ -119,6 +119,16 @@ public abstract class InvocationTask extends Task return StringUtil.unStudlyName(method.getName()).toUpperCase(); } + public String getSenderMethodName () + { + String mname = method.getName(); + if (mname.startsWith("received")) { + return "send" + mname.substring("received".length()); + } else { + return mname; + } + } + public String getArgList (boolean providerMode) { StringBuffer buf = new StringBuffer(); diff --git a/src/java/com/threerings/presents/tools/decoder.tmpl b/src/java/com/threerings/presents/tools/decoder.tmpl new file mode 100644 index 000000000..aa8962c55 --- /dev/null +++ b/src/java/com/threerings/presents/tools/decoder.tmpl @@ -0,0 +1,52 @@ +package $package; + +#foreach ($import in $imports) +import $import; +#end + +/** + * Dispatches calls to a {@link ${name}Receiver} instance. + */ +public class ${name}Decoder extends InvocationDecoder +{ + /** The generated hash code used to identify this receiver class. */ + public static final String RECEIVER_CODE = "$receiver_code"; + +#foreach ($m in $methods) + /** The method id used to dispatch {@link ${name}Receiver#$m.method.name} + * notifications. */ + public static final int $m.code = $velocityCount; + +#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 String getReceiverCode () + { + return RECEIVER_CODE; + } + + // documentation inherited + public void dispatchNotification (int methodId, Object[] args) + { + switch (methodId) { +#foreach ($m in $methods) + case $m.code: + ((${name}Receiver)receiver).${m.method.name}( + $m.getUnwrappedArgList(true) + ); + return; + +#end + default: + super.dispatchNotification(methodId, args); + } + } +} diff --git a/src/java/com/threerings/presents/tools/sender.tmpl b/src/java/com/threerings/presents/tools/sender.tmpl new file mode 100644 index 000000000..2a6190955 --- /dev/null +++ b/src/java/com/threerings/presents/tools/sender.tmpl @@ -0,0 +1,27 @@ +package $package; + +#foreach ($import in $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 in $methods) + /** + * Issues a notification that will result in a call to {@link + * ${name}Receiver#$m.method.name} on a client. + */ + public static void $m.senderMethodName ( + ClientObject target, $m.getArgList(false)) + { + sendNotification( + target, ${name}Decoder.RECEIVER_CODE, ${name}Decoder.$m.code, + new Object[] { $m.getWrappedArgList(false) }); + } + +#end +}