// // $Id$ package com.threerings.presents.tools; import java.io.File; import java.io.StringWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import org.apache.velocity.VelocityContext; import com.samskivert.util.CollectionUtil; import com.samskivert.util.SortableArrayList; import com.samskivert.util.StringUtil; import com.threerings.presents.client.Client; import com.threerings.presents.client.InvocationService.InvocationListener; import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.InvocationMarshaller; import com.threerings.presents.dobj.InvocationResponseEvent; import com.threerings.presents.server.InvocationDispatcher; import com.threerings.presents.server.InvocationException; /** * An Ant task for generating invocation service marshalling and * unmarshalling classes. */ public class GenServiceTask extends InvocationTask { /** Used to keep track of custom InvocationListener derivations. */ public static class ServiceListener { public Class listener; public ArrayList methods = new ArrayList(); public ServiceListener (Class service, Class listener, HashMap imports) { this.listener = listener; Method[] methdecls = listener.getDeclaredMethods(); for (int ii = 0; ii < methdecls.length; ii++) { Method m = methdecls[ii]; // service interface methods must be public and abstract if (!Modifier.isPublic(m.getModifiers()) && !Modifier.isAbstract(m.getModifiers())) { continue; } methods.add(new ServiceMethod(service, m, imports)); } } public String getName () { String name = simpleName(listener); name = StringUtil.replace(name, "Listener", ""); int didx = name.indexOf("."); return name.substring(didx+1); } } // documentation inherited protected void processService (File source, Class service) { System.out.println("Processing " + service.getName() + "..."); String sname = service.getName(); String spackage = ""; int didx = sname.lastIndexOf("."); if (didx != -1) { spackage = sname.substring(0, didx); sname = sname.substring(didx+1); } // verify that the service class name is as we expect it to be if (!sname.endsWith("Service")) { System.err.println("Cannot process '" + sname + "':"); System.err.println( "Service classes must be named SomethingService."); return; } HashMap imports = new HashMap(); ArrayList methods = new ArrayList(); ArrayList listeners = new ArrayList(); // we need to import the service itself imports.put(importify(service.getName()), Boolean.TRUE); // look through and locate our service methods, also locating any // custom InvocationListener derivations along the way Method[] methdecls = service.getDeclaredMethods(); for (int ii = 0; ii < methdecls.length; ii++) { Method m = methdecls[ii]; // service interface methods must be public and abstract if (!Modifier.isPublic(m.getModifiers()) && !Modifier.isAbstract(m.getModifiers())) { continue; } // check this method for custom listener declarations Class[] args = m.getParameterTypes(); for (int aa = 0; aa < args.length; aa++) { if (InvocationListener.class.isAssignableFrom(args[aa]) && simpleName(args[aa]).startsWith(sname + ".")) { listeners.add(new ServiceListener( service, args[aa], imports)); } } methods.add(new ServiceMethod(service, m, imports)); } generateMarshaller(source, sname, spackage, methods, listeners, imports.keySet().iterator()); generateDispatcher(source, sname, spackage, methods, imports.keySet().iterator()); } protected void generateMarshaller ( File source, String sname, String spackage, ArrayList methods, ArrayList listeners, Iterator imports) { String name = StringUtil.replace(sname, "Service", ""); String mname = StringUtil.replace(sname, "Service", "Marshaller"); String mpackage = StringUtil.replace(spackage, ".client", ".data"); // construct our imports list SortableArrayList implist = new SortableArrayList(); CollectionUtil.addAll(implist, imports); checkedAdd(implist, Client.class.getName()); checkedAdd(implist, InvocationMarshaller.class.getName()); checkedAdd(implist, InvocationResponseEvent.class.getName()); implist.sort(); VelocityContext ctx = new VelocityContext(); ctx.put("name", name); ctx.put("package", mpackage); ctx.put("methods", methods); ctx.put("listeners", listeners); ctx.put("imports", implist); 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 = StringUtil.replace(mpath, "/client/", "/data/"); writeFile(mpath, sw.toString()); } catch (Exception e) { System.err.println("Failed processing template"); e.printStackTrace(System.err); } } protected void generateDispatcher ( File source, String sname, String spackage, ArrayList methods, Iterator imports) { String name = StringUtil.replace(sname, "Service", ""); String dname = StringUtil.replace(sname, "Service", "Dispatcher"); String dpackage = StringUtil.replace(spackage, ".client", ".server"); // construct our imports list SortableArrayList implist = new SortableArrayList(); CollectionUtil.addAll(implist, imports); checkedAdd(implist, ClientObject.class.getName()); checkedAdd(implist, InvocationMarshaller.class.getName()); checkedAdd(implist, InvocationDispatcher.class.getName()); checkedAdd(implist, InvocationException.class.getName()); String mname = StringUtil.replace(sname, "Service", "Marshaller"); String mpackage = StringUtil.replace(spackage, ".client", ".data"); checkedAdd(implist, mpackage + "." + mname); implist.sort(); VelocityContext ctx = new VelocityContext(); ctx.put("name", name); ctx.put("package", dpackage); ctx.put("methods", methods); ctx.put("imports", implist); try { StringWriter sw = new StringWriter(); _velocity.mergeTemplate(DISPATCHER_TMPL, "UTF-8", ctx, sw); // determine the path to our marshaller file String mpath = source.getPath(); mpath = StringUtil.replace(mpath, "Service", "Dispatcher"); mpath = StringUtil.replace(mpath, "/client/", "/server/"); writeFile(mpath, sw.toString()); } catch (Exception e) { System.err.println("Failed processing template"); e.printStackTrace(System.err); } } /** Specifies the path to the marshaller template. */ protected static final String MARSHALLER_TMPL = "com/threerings/presents/tools/marshaller.tmpl"; /** Specifies the path to the dispatcher template. */ protected static final String DISPATCHER_TMPL = "com/threerings/presents/tools/dispatcher.tmpl"; }