From 2850a15acd06ee4dd3a1f8744c8476d0b2f632ff Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 26 Nov 2004 22:39:09 +0000 Subject: [PATCH] Factored out the code that will be shared between GenServiceTask and GenReceiverTask (not yet implemented). Modified the task so that it can load the service classes via a classpath declared inside ant, avoiding the need to put project classes in Ant's classpath. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3240 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- bin/genservice | 467 ------------------ .../presents/tools/GenServiceTask.java | 359 +------------- .../presents/tools/InvocationTask.java | 380 ++++++++++++++ 3 files changed, 382 insertions(+), 824 deletions(-) delete mode 100755 bin/genservice create mode 100644 src/java/com/threerings/presents/tools/InvocationTask.java diff --git a/bin/genservice b/bin/genservice deleted file mode 100755 index 62860acd3..000000000 --- a/bin/genservice +++ /dev/null @@ -1,467 +0,0 @@ -#!/usr/bin/perl -w -# -# $Id: genservice,v 1.6 2004/10/13 19:29:12 andrzej Exp $ -# -# This script is used to generate invocation service marshaller and -# unmarshaller classes based on invocation service 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, - ); - -# some standard business -my $ISVC_CNAME = "com.threerings.presents.client.InvocationService"; - -# make sure we have at least one source file argument -die $usage unless (@ARGV); - -# locate a few necessary other files -my $script = basename($0); -my $rootdir = dirname($0); $rootdir =~ s:bin$:.:g; -my $marshtmpl = "$rootdir/lib/marshaller.tmpl"; -my $disptmpl = "$rootdir/lib/dispatcher.tmpl"; - -# now process our source files -my %imports; -my $srcfile; -my $spackage; - -while ($srcfile = shift) { - # do a quick check to see if the marshaller file is newer than the - # service file in which case we can stop now - my $tpath = $srcfile; - $tpath =~ s/Service/Marshaller/; - $tpath =~ s:/client/:/data/:; - 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 @listeners; - my $listener; - - 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 InvocationService, we - # don't want to do nothin' - die "$script: $ifcname does not extend " . - "InvocationService\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); - my @unwrap_args = @trimmed_args; - shift @unwrap_args; - push @methods, { - "mname" => $methname, - "mcode" => to_caps($methname), - "mcodeval" => $methcount++, - "arglist" => gen_arglist(@trimmed_args), - "wrapped_args" => wrap_arglist(1, @trimmed_args), - "unwrapped_args" => unwrap_arglist(@unwrap_args), - "listener_args" => gen_listener_args(@trimmed_args), - }; - - } elsif (/^\s+public static interface \S+\. (\S+) extends (\S+)\. (\S+)/) { - my $inner = $1; - my $eouter = $2; - my $einner = $3; - - if ($eouter ne $ISVC_CNAME) { - warn "Unrecognized inner interface $inner (extends $2.$3). " . - "Skipping.\n"; - undef $listener; - next; - } - - die "$script: Invalid callback interface name $inner.\n" . - "Must be of the form Listener\n" - if ($inner !~ m/Listener$/); - - # create a new listener table to hold info on this listener class - $inner =~ s:Listener$::; - my @lmethods = (); - $listener = { - "name" => $inner, - "methods" => \@lmethods, - }; - push @listeners, $listener; - $lmethcount = 0; - - } elsif (/^ public abstract void (\S+)\(([^\)]*)\)/) { - my $methname = $1; - my $rawargs = $2; $rawargs =~ s:\$:.:g; - my @methargs = split(/, /, $rawargs); - - # skip this method declaration if we're not parsing a valid - # listener inner interface declaration - next unless (defined $listener); - - grab_imports(@methargs); - my @trimmed_args = trim_args(@methargs); - my $lmethod = { - "mname" => $methname, - "mcode" => to_caps($methname), - "mcodeval" => ++$lmethcount, - "arglist" => gen_arglist(@trimmed_args), - "wrapped_args" => wrap_arglist(0, @trimmed_args), - "unwrapped_args" => unwrap_arglist(@trimmed_args), - }; - push @{$listener->{"methods"}}, $lmethod; - - } 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 service name - my $svcname = substr($ifcname, length($ifcpkg)+1); - $svcname =~ s/Service$//g; - die "$script: Unable to discern service name from $ifcname.\n". - "Service interface definitions should be named Service.\n" - if ($svcname eq $ifcname); - - # assume everything's in the same package to start - my $marshpkg = $ifcpkg; - my $disppkg = $ifcpkg; - - # if the interface is in a 'client' package, we'll put the marshaller - # in the associated 'data' package and the dispatcher in the 'server' - # package - if ($ifcpkg =~ m/\.client$/) { - $marshpkg =~ s/\.client$/.data/g; - $disppkg =~ 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 Marshaller class definition - my $marshpath = $marshpkg; - $marshpath =~ s:\.:/:g; - $marshpath = "$sourcedir/$marshpath/$svcname" . "Marshaller.java"; - - print "$script: Generating marshaller:\n" . - " $marshpkg.${svcname}Marshaller in\n" . - " $marshpath\n" if ($verbose); - - # populate a data table with all the data we'll need to fill out our - # marshaller template file - my %mimports = %imports; - $mimports{"com.threerings.presents.client.Client"} = 1; - $mimports{"com.threerings.presents.data.InvocationMarshaller"} = 1; - $mimports{"com.threerings.presents.dobj.InvocationResponseEvent"} = 1; - my @mimportsl = sort keys %mimports; - my %mdata = ( "idstr" => slurp_idstr($marshpath), - "imports" => \@mimportsl, - "name" => $svcname, - "package" => $marshpkg, - "listeners" => \@listeners, - "methods" => \@methods, - "genstamp" => $genstamp, - ); - - # my $dumper = Dumpvalue->new; - # $dumper->dumpValue(\%mdata); - - $tt->process($marshtmpl, \%mdata, $marshpath) or - die "$script: Unable to process template file '$marshtmpl': " . - $tt->error() . "\n"; - - # now we generate the Dispatcher class definition - my $disppath = $disppkg; - $disppath =~ s:\.:/:g; - $disppath = "$sourcedir/$disppath/$svcname" . "Dispatcher.java"; - - print "$script: Generating dispatcher:\n" . - " $disppkg.${svcname}Dispatcher in\n" . - " $disppath\n" if ($verbose); - - # populate a data table with all the data we'll need to fill out our - # dispatcher template file - my %dimports = %imports; - $dimports{"com.threerings.presents.data.ClientObject"} = 1; - $dimports{"com.threerings.presents.data.InvocationMarshaller"} = 1; - $dimports{"com.threerings.presents.server.InvocationDispatcher"} = 1; - $dimports{"com.threerings.presents.server.InvocationException"} = 1; - $dimports{"$marshpkg.${svcname}Marshaller"} = 1; - my @dimportsl = sort keys %dimports; - my %ddata = ( "idstr" => slurp_idstr($disppath), - "imports" => \@dimportsl, - "name" => $svcname, - "package" => $disppkg, - "listeners" => \@listeners, - "methods" => \@methods, - "genstamp" => $genstamp, - ); - - # my $dumper = Dumpvalue->new; - # $dumper->dumpValue(\%ddata); - - $tt->process($disptmpl, \%ddata, $disppath) or - die "$script: Unable to process template file '$disptmpl': " . - $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; - - # if this class is a listener and not in the same package as the - # service class we're currently processing, we need to import its - # marshaller as well - if ($tclass =~ m/Listener$/ && - $tclass !~ m/^$spackage/ && - $tclass !~ $ISVC_CNAME) { - my $mclass = $tclass; - $mclass =~ s:\.client\.:.data.:g; - $mclass =~ s:Listener:Marshaller:g; - $mclass =~ s:Service:Marshaller:g; - $imports{$mclass} = 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)"; - } elsif ($arg =~ /Listener$/) { - $argvals .= "listener$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; -} - -# -# Generates the necessary data to wrap listeners in listener marshaller -# instances. -# -sub gen_listener_args { - my $arg; - my @listener_args = (); - my $i = 0; - foreach $arg (@_) { - $i++; - next unless ($arg =~ /Listener$/); - - my $mtype = $arg; - if ($mtype eq "InvocationListener") { - $mtype = "ListenerMarshaller"; - } else { - $mtype =~ s:Listener$:Marshaller:; - } - - push (@listener_args, { "mtype" => $mtype, - "aname" => "listener$i", - "oaname" => "arg$i" }); - } - return \@listener_args; -} - -# -# 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/src/java/com/threerings/presents/tools/GenServiceTask.java b/src/java/com/threerings/presents/tools/GenServiceTask.java index f5037e952..d99a4fece 100644 --- a/src/java/com/threerings/presents/tools/GenServiceTask.java +++ b/src/java/com/threerings/presents/tools/GenServiceTask.java @@ -3,40 +3,21 @@ package com.threerings.presents.tools; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; -import java.util.Properties; - -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.DirectoryScanner; -import org.apache.tools.ant.Task; -import org.apache.tools.ant.types.FileSet; import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; import com.samskivert.util.CollectionUtil; -import com.samskivert.util.ObjectUtil; import com.samskivert.util.SortableArrayList; import com.samskivert.util.StringUtil; -import com.samskivert.velocity.VelocityUtil; import com.threerings.presents.client.Client; import com.threerings.presents.client.InvocationService.InvocationListener; @@ -50,186 +31,8 @@ import com.threerings.presents.server.InvocationException; * An Ant task for generating invocation service marshalling and * unmarshalling classes. */ -public class GenServiceTask extends Task +public class GenServiceTask extends InvocationTask { - /** Used to keep track of invocation service method listener arguments. */ - public static class ListenerArgument - { - public int index; - - public Class listener; - - public ListenerArgument (int index, Class listener) - { - this.index = index+1; - this.listener = listener; - } - - public String getMarshaller () - { - String name = simpleName(listener); - // handle ye olde special case - if (name.equals("InvocationService.InvocationListener")) { - return "ListenerMarshaller"; - } - name = StringUtil.replace(name, "Service", "Marshaller"); - return StringUtil.replace(name, "Listener", "Marshaller"); - } - } - - /** Used to keep track of invocation service methods. */ - public static class ServiceMethod - { - public Method method; - - public ArrayList listenerArgs = new ArrayList(); - - public ServiceMethod (Class service, Method method, HashMap imports) - { - this.method = method; - - // we need to look through our arguments and note any needed - // imports in the supplied table - Class[] args = method.getParameterTypes(); - for (int ii = 0; ii < args.length; ii++) { - Class arg = args[ii]; - while (arg.isArray()) { - arg = arg.getComponentType(); - } - - // if this is a listener, we need to add a listener - // argument info for it - if (InvocationListener.class.isAssignableFrom(arg)) { - listenerArgs.add(new ListenerArgument(ii, arg)); - } - - // if it's not primitive, global or in our package, we - // should add an import statement for it - if (arg.isPrimitive() || - arg.getName().startsWith("java.lang") || - ObjectUtil.equals( - arg.getPackage(), service.getPackage())) { - continue; - } - imports.put(importify(arg.getName()), Boolean.TRUE); - - // if it's a listener and not one of the special - // InvocationService listeners, we need to import its - // marshaller as well - if (InvocationListener.class.isAssignableFrom(arg) && - !simpleName(arg).startsWith("InvocationService")) { - String mname = arg.getName(); - mname = StringUtil.replace(mname, "Service", "Marshaller"); - mname = StringUtil.replace(mname, "Listener", "Marshaller"); - mname = StringUtil.replace(mname, ".client.", ".data."); - imports.put(importify(mname), Boolean.TRUE); - } - } - } - - public String getCode () - { - return StringUtil.unStudlyName(method.getName()).toUpperCase(); - } - - public String getArgList () - { - StringBuffer buf = new StringBuffer(); - Class[] args = method.getParameterTypes(); - for (int ii = 0; ii < args.length; ii++) { - if (buf.length() > 0) { - buf.append(", "); - } - buf.append(simpleName(args[ii])).append(" arg").append(ii+1); - } - return buf.toString(); - } - - public String getWrappedArgList (boolean skipFirst) - { - StringBuffer buf = new StringBuffer(); - Class[] args = method.getParameterTypes(); - for (int ii = (skipFirst ? 1 : 0); ii < args.length; ii++) { - if (buf.length() > 0) { - buf.append(", "); - } - buf.append(wrapArgument(args[ii], ii+1)); - } - return buf.toString(); - } - - public boolean hasArgs () - { - return (method.getParameterTypes().length > 1); - } - - public String getUnwrappedArgList (boolean listenerMode) - { - StringBuffer buf = new StringBuffer(); - Class[] args = method.getParameterTypes(); - for (int ii = (listenerMode ? 0 : 1); ii < args.length; ii++) { - if (buf.length() > 0) { - buf.append(", "); - } - buf.append(unwrapArgument(args[ii], listenerMode ? ii : ii-1, - listenerMode)); - } - return buf.toString(); - } - - protected String wrapArgument (Class clazz, int index) - { - if (clazz == Boolean.TYPE) { - return "new Boolean(arg" + index + ")"; - } else if (clazz == Byte.TYPE) { - return "new Byte(arg" + index + ")"; - } else if (clazz == Character.TYPE) { - return "new Character(arg" + index + ")"; - } else if (clazz == Short.TYPE) { - return "new Short(arg" + index + ")"; - } else if (clazz == Integer.TYPE) { - return "new Integer(arg" + index + ")"; - } else if (clazz == Long.TYPE) { - return "new Long(arg" + index + ")"; - } else if (clazz == Float.TYPE) { - return "new Float(arg" + index + ")"; - } else if (clazz == Double.TYPE) { - return "new Double(arg" + index + ")"; - } else if (InvocationListener.class.isAssignableFrom(clazz)) { - return "listener" + index; - } else { - return "arg" + index; - } - } - - protected String unwrapArgument ( - Class clazz, int index, boolean listenerMode) - { - if (clazz == Boolean.TYPE) { - return "((Boolean)args[" + index + "]).booleanValue()"; - } else if (clazz == Byte.TYPE) { - return "((Byte)args[" + index + "]).byteValue()"; - } else if (clazz == Character.TYPE) { - return "((Character)args[" + index + "]).charValue()"; - } else if (clazz == Short.TYPE) { - return "((Short)args[" + index + "]).shortValue()"; - } else if (clazz == Integer.TYPE) { - return "((Integer)args[" + index + "]).intValue()"; - } else if (clazz == Long.TYPE) { - return "((Long)args[" + index + "]).longValue()"; - } else if (clazz == Float.TYPE) { - return "((Float)args[" + index + "]).floatValue()"; - } else if (clazz == Double.TYPE) { - return "((Double)args[" + index + "]).doubleValue()"; - } else if (listenerMode && - InvocationListener.class.isAssignableFrom(clazz)) { - return "listener" + index; - } else { - return "(" + simpleName(clazz) + ")args[" + index + "]"; - } - } - } - /** Used to keep track of custom InvocationListener derivations. */ public static class ServiceListener { @@ -261,107 +64,7 @@ public class GenServiceTask extends Task } } - /** - * Adds a nested <fileset> element which enumerates service - * declaration source files. - */ - public void addFileset (FileSet set) - { - _filesets.add(set); - } - - /** - * Configures us with a header file that we'll prepend to all - * generated source files. - */ - public void setHeader (File header) - { - try { - _header = IOUtils.toString(new FileReader(header)); - } catch (IOException ioe) { - System.err.println("Unabled to load header '" + header + ": " + - ioe.getMessage()); - } - } - - /** - * Performs the actual work of the task. - */ - public void execute () throws BuildException - { - try { - _velocity = VelocityUtil.createEngine(); - } catch (Exception e) { - throw new BuildException("Failure initializing Velocity", e); - } - - ArrayList files = new ArrayList(); - for (Iterator iter = _filesets.iterator(); iter.hasNext(); ) { - FileSet fs = (FileSet)iter.next(); - DirectoryScanner ds = fs.getDirectoryScanner(getProject()); - File fromDir = fs.getDir(getProject()); - String[] srcFiles = ds.getIncludedFiles(); - for (int f = 0; f < srcFiles.length; f++) { - processService(new File(fromDir, srcFiles[f])); - } - } - } - - /** - * Processes an invocation service source file. - */ - protected void processService (File source) - { - // System.err.println("Processing " + source + "..."); - // load up the file and determine it's package and classname - String pkgname = null, name = null; - try { - BufferedReader bin = new BufferedReader(new FileReader(source)); - String line; - while ((line = bin.readLine()) != null) { - Matcher pm = PACKAGE_PATTERN.matcher(line); - if (pm.find()) { - pkgname = pm.group(1); - } - Matcher nm = NAME_PATTERN.matcher(line); - if (nm.find()) { - name = nm.group(1); - break; - } - } - bin.close(); - - // make sure we found something - if (name == null) { - System.err.println( - "Unable to locate interface name in " + source + "."); - return; - } - - // prepend the package name to get a name we can Class.forName() - if (pkgname != null) { - name = pkgname + "." + name; - } - - } catch (Exception e) { - System.err.println( - "Failed to parse " + source + ": " + e.getMessage()); - } - - try { - processService(source, Class.forName(name)); - } catch (Exception e) { - System.err.println( - "Failed to load " + name + ": " + e.getMessage()); - System.err.println("Make sure the classes for which you will " + - "be generating interfaces are"); - System.err.println("in ant's classpath."); - } - } - - /** - * Processes a resolved invocation service class instance. - */ + // documentation inherited protected void processService (File source, Class service) { System.out.println("Processing " + service.getName() + "..."); @@ -410,11 +113,6 @@ public class GenServiceTask extends Task methods.add(new ServiceMethod(service, m, imports)); } -// String dname = StringUtil.replace(sname, "Service", "Dispatcher"); -// String dpackage = StringUtil.replace(spackage, ".client", ".server"); -// String pname = StringUtil.replace(sname, "Service", "Provider"); -// String ppackage = StringUtil.replace(spackage, ".client", ".server"); - generateMarshaller(source, sname, spackage, methods, listeners, imports.keySet().iterator()); generateDispatcher(source, sname, spackage, methods, @@ -453,7 +151,6 @@ public class GenServiceTask extends Task mpath = StringUtil.replace(mpath, "Service", "Marshaller"); mpath = StringUtil.replace(mpath, "/client/", "/data/"); - System.out.println("Generating " + mname + "..."); writeFile(mpath, sw.toString()); } catch (Exception e) { @@ -497,7 +194,6 @@ public class GenServiceTask extends Task mpath = StringUtil.replace(mpath, "Service", "Dispatcher"); mpath = StringUtil.replace(mpath, "/client/", "/server/"); - System.out.println("Generating " + dname + "..."); writeFile(mpath, sw.toString()); } catch (Exception e) { @@ -506,57 +202,6 @@ public class GenServiceTask extends Task } } - protected void writeFile (String path, String data) - throws IOException - { - if (_header != null) { - data = _header + data; - } - FileUtils.writeStringToFile(new File(path), data, "UTF-8"); - } - - protected static void checkedAdd (SortableArrayList list, String value) - { - if (!list.contains(value)) { - list.add(value); - } - } - - protected static String simpleName (Class clazz) - { - if (clazz.isArray()) { - return simpleName(clazz.getComponentType()) + "[]"; - } else { - Package pkg = clazz.getPackage(); - int offset = (pkg == null) ? 0 : pkg.getName().length()+1; - String name = clazz.getName().substring(offset); - return StringUtil.replace(name, "$", "."); - } - } - - protected static String importify (String name) - { - int didx = name.indexOf("$"); - return (didx == -1) ? name : name.substring(0, didx); - } - - /** A list of filesets that contain tile images. */ - protected ArrayList _filesets = new ArrayList(); - - /** A header to put on all generated source files. */ - protected String _header; - - /** Used to generate source files from templates. */ - protected VelocityEngine _velocity; - - /** A regular expression for matching the package declaration. */ - protected static final Pattern PACKAGE_PATTERN = - Pattern.compile("^\\s*package\\s+(\\S+)\\W"); - - /** A regular expression for matching the interface declaration. */ - protected static final Pattern NAME_PATTERN = - Pattern.compile("^\\s*public\\s+interface\\s+(\\S+)\\W"); - /** Specifies the path to the marshaller template. */ protected static final String MARSHALLER_TMPL = "com/threerings/presents/tools/marshaller.tmpl"; diff --git a/src/java/com/threerings/presents/tools/InvocationTask.java b/src/java/com/threerings/presents/tools/InvocationTask.java new file mode 100644 index 000000000..9ffa43e86 --- /dev/null +++ b/src/java/com/threerings/presents/tools/InvocationTask.java @@ -0,0 +1,380 @@ +// +// $Id$ + +package com.threerings.presents.tools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import java.lang.reflect.Method; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.ClasspathUtils; + +import org.apache.velocity.app.VelocityEngine; + +import com.samskivert.util.ObjectUtil; +import com.samskivert.util.SortableArrayList; +import com.samskivert.util.StringUtil; +import com.samskivert.velocity.VelocityUtil; + +import com.threerings.presents.client.InvocationService.InvocationListener; + +/** + * A base Ant task for generating invocation service related marshalling + * and unmarshalling classes. + */ +public abstract class InvocationTask extends Task +{ + /** Used to keep track of invocation service method listener arguments. */ + public static class ListenerArgument + { + public int index; + + public Class listener; + + public ListenerArgument (int index, Class listener) + { + this.index = index+1; + this.listener = listener; + } + + public String getMarshaller () + { + String name = simpleName(listener); + // handle ye olde special case + if (name.equals("InvocationService.InvocationListener")) { + return "ListenerMarshaller"; + } + name = StringUtil.replace(name, "Service", "Marshaller"); + return StringUtil.replace(name, "Listener", "Marshaller"); + } + } + + /** Used to keep track of invocation service methods. */ + public static class ServiceMethod + { + public Method method; + + public ArrayList listenerArgs = new ArrayList(); + + public ServiceMethod (Class service, Method method, HashMap imports) + { + this.method = method; + + // we need to look through our arguments and note any needed + // imports in the supplied table + Class[] args = method.getParameterTypes(); + for (int ii = 0; ii < args.length; ii++) { + Class arg = args[ii]; + while (arg.isArray()) { + arg = arg.getComponentType(); + } + + // if this is a listener, we need to add a listener + // argument info for it + if (InvocationListener.class.isAssignableFrom(arg)) { + listenerArgs.add(new ListenerArgument(ii, arg)); + } + + // if it's not primitive, global or in our package, we + // should add an import statement for it + if (arg.isPrimitive() || + arg.getName().startsWith("java.lang") || + ObjectUtil.equals( + arg.getPackage(), service.getPackage())) { + continue; + } + imports.put(importify(arg.getName()), Boolean.TRUE); + + // if it's a listener and not one of the special + // InvocationService listeners, we need to import its + // marshaller as well + if (InvocationListener.class.isAssignableFrom(arg) && + !simpleName(arg).startsWith("InvocationService")) { + String mname = arg.getName(); + mname = StringUtil.replace(mname, "Service", "Marshaller"); + mname = StringUtil.replace(mname, "Listener", "Marshaller"); + mname = StringUtil.replace(mname, ".client.", ".data."); + imports.put(importify(mname), Boolean.TRUE); + } + } + } + + public String getCode () + { + return StringUtil.unStudlyName(method.getName()).toUpperCase(); + } + + public String getArgList () + { + StringBuffer buf = new StringBuffer(); + Class[] args = method.getParameterTypes(); + for (int ii = 0; ii < args.length; ii++) { + if (buf.length() > 0) { + buf.append(", "); + } + buf.append(simpleName(args[ii])).append(" arg").append(ii+1); + } + return buf.toString(); + } + + public String getWrappedArgList (boolean skipFirst) + { + StringBuffer buf = new StringBuffer(); + Class[] args = method.getParameterTypes(); + for (int ii = (skipFirst ? 1 : 0); ii < args.length; ii++) { + if (buf.length() > 0) { + buf.append(", "); + } + buf.append(wrapArgument(args[ii], ii+1)); + } + return buf.toString(); + } + + public boolean hasArgs () + { + return (method.getParameterTypes().length > 1); + } + + public String getUnwrappedArgList (boolean listenerMode) + { + StringBuffer buf = new StringBuffer(); + Class[] args = method.getParameterTypes(); + for (int ii = (listenerMode ? 0 : 1); ii < args.length; ii++) { + if (buf.length() > 0) { + buf.append(", "); + } + buf.append(unwrapArgument(args[ii], listenerMode ? ii : ii-1, + listenerMode)); + } + return buf.toString(); + } + + protected String wrapArgument (Class clazz, int index) + { + if (clazz == Boolean.TYPE) { + return "new Boolean(arg" + index + ")"; + } else if (clazz == Byte.TYPE) { + return "new Byte(arg" + index + ")"; + } else if (clazz == Character.TYPE) { + return "new Character(arg" + index + ")"; + } else if (clazz == Short.TYPE) { + return "new Short(arg" + index + ")"; + } else if (clazz == Integer.TYPE) { + return "new Integer(arg" + index + ")"; + } else if (clazz == Long.TYPE) { + return "new Long(arg" + index + ")"; + } else if (clazz == Float.TYPE) { + return "new Float(arg" + index + ")"; + } else if (clazz == Double.TYPE) { + return "new Double(arg" + index + ")"; + } else if (InvocationListener.class.isAssignableFrom(clazz)) { + return "listener" + index; + } else { + return "arg" + index; + } + } + + protected String unwrapArgument ( + Class clazz, int index, boolean listenerMode) + { + if (clazz == Boolean.TYPE) { + return "((Boolean)args[" + index + "]).booleanValue()"; + } else if (clazz == Byte.TYPE) { + return "((Byte)args[" + index + "]).byteValue()"; + } else if (clazz == Character.TYPE) { + return "((Character)args[" + index + "]).charValue()"; + } else if (clazz == Short.TYPE) { + return "((Short)args[" + index + "]).shortValue()"; + } else if (clazz == Integer.TYPE) { + return "((Integer)args[" + index + "]).intValue()"; + } else if (clazz == Long.TYPE) { + return "((Long)args[" + index + "]).longValue()"; + } else if (clazz == Float.TYPE) { + return "((Float)args[" + index + "]).floatValue()"; + } else if (clazz == Double.TYPE) { + return "((Double)args[" + index + "]).doubleValue()"; + } else if (listenerMode && + InvocationListener.class.isAssignableFrom(clazz)) { + return "listener" + index; + } else { + return "(" + simpleName(clazz) + ")args[" + index + "]"; + } + } + } + + /** + * Adds a nested <fileset> element which enumerates service + * declaration source files. + */ + public void addFileset (FileSet set) + { + _filesets.add(set); + } + + /** + * Configures us with a header file that we'll prepend to all + * generated source files. + */ + public void setHeader (File header) + { + try { + _header = IOUtils.toString(new FileReader(header)); + } catch (IOException ioe) { + System.err.println("Unabled to load header '" + header + ": " + + ioe.getMessage()); + } + } + + /** Configures our classpath which we'll use to load service classes. */ + public void setClasspathref (Reference pathref) + { + _cloader = ClasspathUtils.getClassLoaderForPath( + getProject(), pathref); + } + + /** Performs the actual work of the task. */ + public void execute () throws BuildException + { + try { + _velocity = VelocityUtil.createEngine(); + } catch (Exception e) { + throw new BuildException("Failure initializing Velocity", e); + } + + ArrayList files = new ArrayList(); + for (Iterator iter = _filesets.iterator(); iter.hasNext(); ) { + FileSet fs = (FileSet)iter.next(); + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + File fromDir = fs.getDir(getProject()); + String[] srcFiles = ds.getIncludedFiles(); + for (int f = 0; f < srcFiles.length; f++) { + processService(new File(fromDir, srcFiles[f])); + } + } + } + + /** Processes an invocation service source file. */ + protected void processService (File source) + { + // System.err.println("Processing " + source + "..."); + // load up the file and determine it's package and classname + String pkgname = null, name = null; + try { + BufferedReader bin = new BufferedReader(new FileReader(source)); + String line; + while ((line = bin.readLine()) != null) { + Matcher pm = PACKAGE_PATTERN.matcher(line); + if (pm.find()) { + pkgname = pm.group(1); + } + Matcher nm = NAME_PATTERN.matcher(line); + if (nm.find()) { + name = nm.group(1); + break; + } + } + bin.close(); + + // make sure we found something + if (name == null) { + System.err.println( + "Unable to locate interface name in " + source + "."); + return; + } + + // prepend the package name to get a name we can Class.forName() + if (pkgname != null) { + name = pkgname + "." + name; + } + + } catch (Exception e) { + System.err.println( + "Failed to parse " + source + ": " + e.getMessage()); + } + + try { + processService(source, _cloader.loadClass(name)); + } catch (Exception e) { + System.err.println( + "Failed to load " + name + ": " + e.getMessage()); + System.err.println("Make sure the classes for which you will " + + "be generating interfaces are"); + System.err.println("in ant's classpath."); + } + } + + /** Processes a resolved invocation service class instance. */ + protected abstract void processService (File source, Class service); + + protected void writeFile (String path, String data) + throws IOException + { + if (_header != null) { + data = _header + data; + } + FileUtils.writeStringToFile(new File(path), data, "UTF-8"); + } + + protected static void checkedAdd (SortableArrayList list, String value) + { + if (!list.contains(value)) { + list.add(value); + } + } + + protected static String simpleName (Class clazz) + { + if (clazz.isArray()) { + return simpleName(clazz.getComponentType()) + "[]"; + } else { + Package pkg = clazz.getPackage(); + int offset = (pkg == null) ? 0 : pkg.getName().length()+1; + String name = clazz.getName().substring(offset); + return StringUtil.replace(name, "$", "."); + } + } + + protected static String importify (String name) + { + int didx = name.indexOf("$"); + return (didx == -1) ? name : name.substring(0, didx); + } + + /** A list of filesets that contain tile images. */ + protected ArrayList _filesets = new ArrayList(); + + /** A header to put on all generated source files. */ + protected String _header; + + /** Used to do our own classpath business. */ + protected ClassLoader _cloader; + + /** Used to generate source files from templates. */ + protected VelocityEngine _velocity; + + /** A regular expression for matching the package declaration. */ + protected static final Pattern PACKAGE_PATTERN = + Pattern.compile("^\\s*package\\s+(\\S+)\\W"); + + /** A regular expression for matching the interface declaration. */ + protected static final Pattern NAME_PATTERN = + Pattern.compile("^\\s*public\\s+interface\\s+(\\S+)\\W"); +}