diff --git a/src/java/com/threerings/presents/tools/GenServiceTask.java b/src/java/com/threerings/presents/tools/GenServiceTask.java index 51527a3ca..f85b3a8ca 100644 --- a/src/java/com/threerings/presents/tools/GenServiceTask.java +++ b/src/java/com/threerings/presents/tools/GenServiceTask.java @@ -10,6 +10,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -17,12 +18,11 @@ import java.util.List; import org.apache.velocity.VelocityContext; -import com.samskivert.util.CollectionUtil; import com.samskivert.util.ComparableArrayList; import com.samskivert.util.StringUtil; import com.threerings.presents.client.Client; -import com.threerings.presents.client.InvocationService.InvocationListener; +import com.threerings.presents.client.InvocationService; import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.InvocationMarshaller; import com.threerings.presents.dobj.InvocationResponseEvent; @@ -91,6 +91,14 @@ public class GenServiceTask extends InvocationTask } } + /** + * Configures the path to our ActionScript source files. + */ + public void setAsroot (File asroot) + { + _asroot = asroot; + } + // documentation inherited public Providerless createProviderless () { @@ -152,18 +160,18 @@ public class GenServiceTask extends InvocationTask methods.sort(); generateMarshaller(source, sname, spackage, methods, listeners, - imports.keySet().iterator()); + imports.keySet()); generateDispatcher(source, sname, spackage, methods, - imports.keySet().iterator()); + imports.keySet()); if (!_providerless.contains(sname)) { generateProvider(source, sname, spackage, methods, listeners, - imports.keySet().iterator()); + imports.keySet()); } } protected void generateMarshaller ( File source, String sname, String spackage, List methods, - List listeners, Iterator imports) + List listeners, Collection imports) { String name = StringUtil.replace(sname, "Service", ""); String mname = StringUtil.replace(sname, "Service", "Marshaller"); @@ -171,7 +179,7 @@ public class GenServiceTask extends InvocationTask // construct our imports list ComparableArrayList implist = new ComparableArrayList(); - CollectionUtil.addAll(implist, imports); + implist.addAll(imports); checkedAdd(implist, Client.class.getName()); checkedAdd(implist, InvocationMarshaller.class.getName()); checkedAdd(implist, InvocationResponseEvent.class.getName()); @@ -184,26 +192,132 @@ public class GenServiceTask extends InvocationTask ctx.put("listeners", listeners); ctx.put("imports", implist); + // determine the path to our marshaller file + String mpath = source.getPath(); + mpath = StringUtil.replace(mpath, "Service", "Marshaller"); + mpath = replacePath(mpath, "/client/", "/data/"); + try { StringWriter sw = new StringWriter(); _velocity.mergeTemplate(MARSHALLER_TMPL, "UTF-8", ctx, sw); - - // determine the path to our marshaller file - String mpath = source.getPath(); - mpath = StringUtil.replace(mpath, "Service", "Marshaller"); - mpath = replacePath(mpath, "/client/", "/data/"); - writeFile(mpath, sw.toString()); } catch (Exception e) { System.err.println("Failed processing template"); e.printStackTrace(System.err); } + + // if we're not configured with an ActionScript source root, don't + // generate the ActionScript versions + if (_asroot == null) { + return; + } + + // add imports for our inner listener interfaces; because they are not + // real inner interfaces, we have to import them in addition to the + // services that (in Java) contained them + for (ServiceListener listener : listeners) { + checkedAdd(implist, listener.listener.getName().replace("$", "_")); + } + Class isil = InvocationService.InvocationListener.class; + checkedAdd(implist, isil.getName().replace("$", "_")); + implist.sort(); + + // now generate ActionScript versions of our marshaller + try { + // make sure our marshaller directory exists + String mppath = mpackage.replace('.', File.separatorChar); + new File(_asroot + File.separator + mppath).mkdirs(); + + // generate an ActionScript version of our marshaller + String ampath = _asroot + File.separator + mppath + + File.separator + mname + ".as"; + StringWriter sw = new StringWriter(); + _velocity.mergeTemplate(AS_MARSHALLER_TMPL, "UTF-8", ctx, sw); + writeFile(ampath, sw.toString()); + + // now generate ActionScript versions of our listener marshallers + // because those have to be in separate files + Class imlm = InvocationMarshaller.ListenerMarshaller.class; + for (ServiceListener listener : listeners) { + // recreate our imports with just what we need here + implist = new ComparableArrayList(); + implist.addAll(imports); + checkedAdd(implist, imlm.getName().replace("$", "_")); + String lname = listener.listener.getName(); + checkedAdd(implist, lname.replace("$", "_")); + implist.sort(); + ctx.put("imports", implist); + + ctx.put("listener", listener); + sw = new StringWriter(); + _velocity.mergeTemplate( + AS_LISTENER_MARSHALLER_TMPL, "UTF-8", ctx, sw); + String aslpath = _asroot + File.separator + mppath + + File.separator + mname + "_" + + listener.getName() + "Marshaller.as"; + writeFile(aslpath, sw.toString()); + } + + } catch (Exception e) { + System.err.println("Failed processing template"); + e.printStackTrace(System.err); + } + + // then make some changes to the context and generate ActionScript + // versions of the service interface itself + implist = new ComparableArrayList(); + implist.addAll(imports); + checkedAdd(implist, Client.class.getName()); + checkedAdd(implist, InvocationService.class.getName()); + checkedAdd(implist, isil.getName().replace("$", "_")); + implist.sort(); + ctx.put("imports", implist); + ctx.put("package", spackage); + + try { + // make sure our service directory exists + String sppath = spackage.replace('.', File.separatorChar); + new File(_asroot + File.separator + sppath).mkdirs(); + + // generate an ActionScript version of our service + String aspath = _asroot + File.separator + sppath + + File.separator + sname + ".as"; + StringWriter sw = new StringWriter(); + _velocity.mergeTemplate(AS_SERVICE_TMPL, "UTF-8", ctx, sw); + writeFile(aspath, sw.toString()); + + // also generate ActionScript versions of any inner listener + // interfaces because those have to be in separate files + for (ServiceListener listener : listeners) { + // recreate our imports with just what we need here + implist = new ComparableArrayList(); + implist.addAll(imports); + checkedAdd(implist, isil.getName().replace("$", "_")); + String lname = listener.listener.getName(); + checkedAdd(implist, lname.replace("$", "_")); + implist.sort(); + ctx.put("imports", implist); + + ctx.put("listener", listener); + sw = new StringWriter(); + _velocity.mergeTemplate( + AS_LISTENER_SERVICE_TMPL, "UTF-8", ctx, sw); + String amlpath = _asroot + File.separator + sppath + + File.separator + sname + "_" + + listener.getName() + "Listener.as"; + writeFile(amlpath, sw.toString()); + } + + } catch (Exception e) { + System.err.println("Failed processing template"); + e.printStackTrace(System.err); + } } protected void generateDispatcher ( File source, String sname, String spackage, List methods, - Iterator imports) + Collection imports) { String name = StringUtil.replace(sname, "Service", ""); String dname = StringUtil.replace(sname, "Service", "Dispatcher"); @@ -211,7 +325,7 @@ public class GenServiceTask extends InvocationTask // construct our imports list ComparableArrayList implist = new ComparableArrayList(); - CollectionUtil.addAll(implist, imports); + implist.addAll(imports); checkedAdd(implist, ClientObject.class.getName()); checkedAdd(implist, InvocationMarshaller.class.getName()); checkedAdd(implist, InvocationDispatcher.class.getName()); @@ -246,7 +360,7 @@ public class GenServiceTask extends InvocationTask protected void generateProvider ( File source, String sname, String spackage, List methods, - List listeners, Iterator imports) + List listeners, Collection imports) { String name = StringUtil.replace(sname, "Service", ""); String mname = StringUtil.replace(sname, "Service", "Provider"); @@ -254,7 +368,7 @@ public class GenServiceTask extends InvocationTask // construct our imports list ComparableArrayList implist = new ComparableArrayList(); - CollectionUtil.addAll(implist, imports); + implist.addAll(imports); checkedAdd(implist, ClientObject.class.getName()); checkedAdd(implist, InvocationProvider.class.getName()); checkedAdd(implist, InvocationException.class.getName()); @@ -284,6 +398,9 @@ public class GenServiceTask extends InvocationTask } } + /** The path to our ActionScript source files. */ + protected File _asroot; + /** Services for which we should not generate provider interfaces. */ protected HashSet _providerless = new HashSet(); @@ -298,4 +415,20 @@ public class GenServiceTask extends InvocationTask /** Specifies the path to the provider template. */ protected static final String PROVIDER_TMPL = "com/threerings/presents/tools/provider.tmpl"; + + /** Specifies the path to the ActionScript service template. */ + protected static final String AS_SERVICE_TMPL = + "com/threerings/presents/tools/service_as.tmpl"; + + /** Specifies the path to the ActionScript listener service template. */ + protected static final String AS_LISTENER_SERVICE_TMPL = + "com/threerings/presents/tools/service_listener_as.tmpl"; + + /** Specifies the path to the ActionScript marshaller template. */ + protected static final String AS_MARSHALLER_TMPL = + "com/threerings/presents/tools/marshaller_as.tmpl"; + + /** Specifies the path to the ActionScript listener marshaller template. */ + protected static final String AS_LISTENER_MARSHALLER_TMPL = + "com/threerings/presents/tools/marshaller_listener_as.tmpl"; } diff --git a/src/java/com/threerings/presents/tools/GenUtil.java b/src/java/com/threerings/presents/tools/GenUtil.java index bff4b6a62..651c6227e 100644 --- a/src/java/com/threerings/presents/tools/GenUtil.java +++ b/src/java/com/threerings/presents/tools/GenUtil.java @@ -101,6 +101,33 @@ public class GenUtil } } + /** + * Returns the name of the supplied class as it would appear in + * ActionScript code using the class (no package prefix, arrays specified + * as ArrayArray). + */ + public static String simpleASName (Class clazz) + { + if (clazz.isArray()) { + return "Array"; + } else if (clazz == Boolean.TYPE) { + return "Boolean"; + } else if (clazz == Byte.TYPE || clazz == Character.TYPE || + clazz == Short.TYPE || clazz == Integer.TYPE) { + return "int"; + } else if (clazz == Long.TYPE) { + return "Long"; + } else if (clazz == Float.TYPE || clazz == Double.TYPE) { + return "Number"; + } else { + String cname = clazz.getName(); + Package pkg = clazz.getPackage(); + int offset = (pkg == null) ? 0 : pkg.getName().length()+1; + String name = cname.substring(offset); + return StringUtil.replace(name, "$", "_"); + } + } + /** * "Boxes" the supplied argument, ie. turning an int into * an Integer object. @@ -155,6 +182,60 @@ public class GenUtil } } + /** + * "Boxes" the supplied argument, ie. turning an int into + * an Integer object. + */ + public static String boxASArgument (Class clazz, String name) + { + if (clazz == Boolean.TYPE) { + return "langBoolean.valueOf(" + name + ")"; + } else if (clazz == Byte.TYPE) { + return "Byte.valueOf(" + name + ")"; + } else if (clazz == Character.TYPE) { + return "Character.valueOf(" + name + ")"; + } else if (clazz == Short.TYPE) { + return "Short.valueOf(" + name + ")"; + } else if (clazz == Integer.TYPE) { + return "Integer.valueOf(" + name + ")"; + } else if (clazz == Long.TYPE) { + return name; // Long is left as is + } else if (clazz == Float.TYPE) { + return "Float.valueOf(" + name + ")"; + } else if (clazz == Double.TYPE) { + return "Double.valueOf(" + name + ")"; + } else { + return name; + } + } + + /** + * "Unboxes" the supplied argument, ie. turning an Integer + * object into an int. + */ + public static String unboxASArgument (Class clazz, String name) + { + if (clazz == Boolean.TYPE) { + return "(" + name + " as langBoolean).value"; + } else if (clazz == Byte.TYPE) { + return "(" + name + " as Byte).value"; + } else if (clazz == Character.TYPE) { + return "(" + name + " as Character).value"; + } else if (clazz == Short.TYPE) { + return "(" + name + " as Short).value"; + } else if (clazz == Integer.TYPE) { + return "(" + name + " as Integer).value"; + } else if (clazz == Long.TYPE) { + return "(" + name + " as Long)"; + } else if (clazz == Float.TYPE) { + return "(" + name + " as Float).value"; + } else if (clazz == Double.TYPE) { + return "(" + name + " as Double).value"; + } else { + return name + " as " + simpleASName(clazz); + } + } + /** * Potentially clones the supplied argument if it is the type that * needs such treatment. diff --git a/src/java/com/threerings/presents/tools/InvocationTask.java b/src/java/com/threerings/presents/tools/InvocationTask.java index 27a08c408..12ec999df 100644 --- a/src/java/com/threerings/presents/tools/InvocationTask.java +++ b/src/java/com/threerings/presents/tools/InvocationTask.java @@ -63,6 +63,11 @@ public abstract class InvocationTask extends Task name = StringUtil.replace(name, "Service", "Marshaller"); return StringUtil.replace(name, "Listener", "Marshaller"); } + + public String getActionScriptMarshaller () + { + return getMarshaller().replace(".", "_"); + } } /** Used to keep track of invocation service methods. */ @@ -106,9 +111,9 @@ public abstract class InvocationTask extends Task // if it's a listener and not one of the special // InvocationService listeners, we need to import its // marshaller as well + String sname = GenUtil.simpleName(arg, null); if (_ilistener.isAssignableFrom(arg) && - !GenUtil.simpleName( - arg, null).startsWith("InvocationService")) { + !sname.startsWith("InvocationService")) { String mname = arg.getName(); mname = StringUtil.replace(mname, "Service", "Marshaller"); mname = StringUtil.replace(mname, "Listener", "Marshaller"); @@ -148,6 +153,20 @@ public abstract class InvocationTask extends Task return buf.toString(); } + public String getASArgList (boolean skipFirst) + { + StringBuilder buf = new StringBuilder(); + Class[] args = method.getParameterTypes(); + for (int ii = skipFirst ? 1 : 0; ii < args.length; ii++) { + if (buf.length() > 0) { + buf.append(", "); + } + buf.append("arg").append(skipFirst ? ii : ii+1).append(" :"); + buf.append(GenUtil.simpleASName(args[ii])); + } + return buf.toString(); + } + public String getWrappedArgList (boolean skipFirst) { StringBuilder buf = new StringBuilder(); @@ -161,6 +180,25 @@ public abstract class InvocationTask extends Task return buf.toString(); } + public String getASWrappedArgList (boolean skipFirst) + { + StringBuilder buf = new StringBuilder(); + Class[] args = method.getParameterTypes(); + for (int ii = (skipFirst ? 1 : 0); ii < args.length; ii++) { + if (buf.length() > 0) { + buf.append(", "); + } + String arg; + if (_ilistener.isAssignableFrom(args[ii])) { + arg = GenUtil.boxASArgument(args[ii], "listener" + (ii+1)); + } else { + arg = GenUtil.boxASArgument(args[ii], "arg" + (ii+1)); + } + buf.append(arg); + } + return buf.toString(); + } + public boolean hasArgs (boolean skipFirst) { return (method.getParameterTypes().length > (skipFirst ? 1 : 0)); @@ -186,6 +224,28 @@ public abstract class InvocationTask extends Task return buf.toString(); } + public String getASUnwrappedArgList (boolean listenerMode) + { + StringBuilder buf = new StringBuilder(); + Class[] args = method.getParameterTypes(); + Type[] ptypes = method.getGenericParameterTypes(); + for (int ii = (listenerMode ? 0 : 1); ii < args.length; ii++) { + if (buf.length() > 0) { + buf.append(", "); + } + String arg; + int argidx = listenerMode ? ii : ii-1; + if (listenerMode && _ilistener.isAssignableFrom(args[ii])) { + arg = "listener" + argidx; + } else { + arg = GenUtil.unboxASArgument( + args[ii], "args[" + argidx + "]"); + } + buf.append(arg); + } + return buf.toString(); + } + protected String boxArgument (Class clazz, int index) { if (_ilistener.isAssignableFrom(clazz)) { diff --git a/src/java/com/threerings/presents/tools/marshaller.tmpl b/src/java/com/threerings/presents/tools/marshaller.tmpl index 31896101a..512be7297 100644 --- a/src/java/com/threerings/presents/tools/marshaller.tmpl +++ b/src/java/com/threerings/presents/tools/marshaller.tmpl @@ -15,7 +15,9 @@ public class ${name}Marshaller extends InvocationMarshaller implements ${name}Service { #foreach ($l in $listeners) - // documentation inherited + /** + * Marshalls results to implementations of {@link ${l.name}Listener}. + */ public static class ${l.name}Marshaller extends ListenerMarshaller implements ${l.name}Listener { @@ -24,7 +26,7 @@ public class ${name}Marshaller extends InvocationMarshaller * responses. */ public static final int $lm.code = $velocityCount; - // documentation inherited from interface + // from interface ${l.name}Marshaller public void $lm.method.name ($lm.getArgList(false)) { _invId = null; @@ -34,7 +36,7 @@ public class ${name}Marshaller extends InvocationMarshaller } #end - // documentation inherited + @Override // from InvocationMarshaller public void dispatchResponse (int methodId, Object[] args) { switch (methodId) { @@ -54,10 +56,13 @@ public class ${name}Marshaller extends InvocationMarshaller #end #foreach ($m in $methods) +#if ($velocityCount > 1) + +#end /** The method id used to dispatch {@link #$m.method.name} requests. */ public static final int $m.code = $velocityCount; - // documentation inherited from interface + // from interface ${name}Service public void $m.method.name ($m.getArgList(false)) { #foreach ($la in $m.listenerArgs) @@ -68,6 +73,5 @@ public class ${name}Marshaller extends InvocationMarshaller $m.getWrappedArgList(true) }); } - #end } diff --git a/src/java/com/threerings/presents/tools/marshaller_as.tmpl b/src/java/com/threerings/presents/tools/marshaller_as.tmpl new file mode 100644 index 000000000..801e8f783 --- /dev/null +++ b/src/java/com/threerings/presents/tools/marshaller_as.tmpl @@ -0,0 +1,39 @@ +package $package { + +import com.threerings.util.*; // for Float, Integer, etc. + +#foreach ($import in $imports) +import $import; +#end + +/** + * Provides the implementation of the {@link ${name}Service} interface + * that marshalls the arguments and delivers the request to the provider + * on the server. Also provides an implementation of the response listener + * interfaces that marshall the response arguments and deliver them back + * to the requesting client. + */ +public class ${name}Marshaller extends InvocationMarshaller + implements ${name}Service +{ +#foreach ($m in $methods) +#if ($velocityCount > 1) + +#end + /** The method id used to dispatch {@link #$m.method.name} requests. */ + public static const $m.code :int = $velocityCount; + + // from interface ${name}Service + public function $m.method.name ($m.getASArgList(false)) :void + { +#foreach ($la in $m.listenerArgs) + var listener$la.index :$la.actionScriptMarshaller = new ${la.actionScriptMarshaller}(); + listener${la.index}.listener = arg$la.index; +#end + sendRequest(arg1, $m.code, [ + $m.getASWrappedArgList(true) + ]); + } +#end +} +} diff --git a/src/java/com/threerings/presents/tools/marshaller_listener_as.tmpl b/src/java/com/threerings/presents/tools/marshaller_listener_as.tmpl new file mode 100644 index 000000000..11ac14e79 --- /dev/null +++ b/src/java/com/threerings/presents/tools/marshaller_listener_as.tmpl @@ -0,0 +1,37 @@ +package $package { + +import com.threerings.util.*; // for Float, Integer, etc. + +#foreach ($import in $imports) +import $import; +#end + +/** + * Marshalls instances of the ${name}Service_${listener.name}Marshaller interface. + */ +public class ${name}Marshaller_${listener.name}Marshaller + extends InvocationMarshaller_ListenerMarshaller +{ +#foreach ($lm in $listener.methods) + /** The method id used to dispatch {@link #$lm.method.name} responses. */ + public static const $lm.code :int = $velocityCount; + +#end + // from InvocationMarshaller_ListenerMarshaller + override public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { +#foreach ($lm in $listener.methods) + case $lm.code: + (listener as ${name}Service_${listener.name}Listener).${lm.method.name}( + ${lm.getASUnwrappedArgList(true)}); + return; + +#end + default: + super.dispatchResponse(methodId, args); + return; + } + } +} +} diff --git a/src/java/com/threerings/presents/tools/service_as.tmpl b/src/java/com/threerings/presents/tools/service_as.tmpl new file mode 100644 index 000000000..e18e29f3b --- /dev/null +++ b/src/java/com/threerings/presents/tools/service_as.tmpl @@ -0,0 +1,20 @@ +package $package { + +#foreach ($import in $imports) +import $import; +#end + +/** + * An ActionScript version of the Java ${name}Service interface. + */ +public interface ${name}Service extends InvocationService +{ +#foreach ($m in $methods) +#if ($velocityCount > 1) + +#end + // from Java interface ${name}Service + function $m.method.name ($m.getASArgList(false)) :void; +#end +} +} diff --git a/src/java/com/threerings/presents/tools/service_listener_as.tmpl b/src/java/com/threerings/presents/tools/service_listener_as.tmpl new file mode 100644 index 000000000..e2e362bfb --- /dev/null +++ b/src/java/com/threerings/presents/tools/service_listener_as.tmpl @@ -0,0 +1,23 @@ +package $package { + +import com.threerings.util.*; // for Float, Integer, etc. + +#foreach ($import in $imports) +import $import; +#end + +/** + * An ActionScript version of the Java ${name}Service_${listener.name}Listener interface. + */ +public interface ${name}Service_${listener.name}Listener + extends InvocationService_InvocationListener +{ +#foreach ($lm in $listener.methods) +#if ($velocityCount > 1) + +#end + // from Java ${name}Service_${listener.name}Listener + function $lm.method.name ($lm.getASArgList(false)) :void +#end +} +}