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
This commit is contained in:
Michael Bayne
2004-11-26 22:39:09 +00:00
parent a04e23e025
commit 2850a15acd
3 changed files with 382 additions and 824 deletions
-467
View File
@@ -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] <sourcedir/foo/bar/FooService.java> [...]\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 (<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 @listeners;
my $listener;
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 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 <foo>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 <foo>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 <foo>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 <foo>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 (<IN>) {
if (/(\$[I][d]: .*\$)/) {
$idstr = $1;
last;
}
}
close(IN);
}
return $idstr;
}
@@ -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 &lt;fileset&gt; 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";
@@ -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 &lt;fileset&gt; 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");
}