Convert Narya (most of the way) over to a Maven Ant task based build. The

ActionScript bits remain belligerent, but the Java stuff is mostly shipshape.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2010-10-22 21:12:29 +00:00
parent 555b865bbf
commit 9d2ca42eac
434 changed files with 163 additions and 208 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,169 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
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 com.google.common.collect.Lists;
/**
* Generates our own ResourceBundle classes.
*
* NOTE: This is not used. We are just using the standard ResourceBundles
* after all.
*/
public class GenActionScriptBundlesTask extends Task
{
public void addFileset (FileSet set)
{
_filesets.add(set);
}
public void setAsroot (File asroot)
{
_asroot = asroot;
}
@Override
public void execute ()
{
// boilerplate
for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (String srcFile : srcFiles) {
try {
processBundle(new File(fromDir, srcFile));
} catch (IOException ioe) {
throw new BuildException(ioe);
}
}
}
}
protected void processBundle (File source)
throws IOException
{
Properties props = new Properties();
props.load(new FileInputStream(source));
String name = source.getName();
name = name.replace('.', '_');
File outfile = new File(_asroot, name + ".as");
PrintWriter out = new PrintWriter(outfile);
out.println("package {");
out.println();
out.println("import com.threerings.util.ResourceBundle;");
out.println();
out.println("// Generated at " + new Date());
out.println("public class " + name + " extends ResourceBundle");
out.println("{");
out.println(" override protected function getContent () :Object");
out.println(" {");
// create an array with all the values, then populate in a loop
out.println(" var data :Array = [");
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String key = saveConvert((String) entry.getKey());
String val = saveConvert((String) entry.getValue());
out.println(" \"" + key + "\", \"" + val + "\",");
}
out.println(" null];");
out.println(" var o :Object = new Object();");
out.println(" for (var ii :int = 0; ii < data.length; ii += 2) {");
out.println(" o[data[ii]] = data[ii + 1];");
out.println(" }");
// // alternate impl: just set each value directly. For non-trivial
// // resource bundles, this generates a larger class after compilation
// out.println(" var o :Object = new Object();");
// for (Map.Entry<Object, Object> entry : props.entrySet()) {
// String key = saveConvert((String) entry.getKey());
// String val = saveConvert((String) entry.getValue());
// out.println(" o[\"" + key + "\"] = \"" + val + "\";");
// }
out.println(" return o;");
out.println(" }");
out.println("}}");
out.close();
}
/**
* Convert a string to be safe to output inside a string constant.
*/
protected String saveConvert (String str)
{
int len = str.length();
StringBuilder buf = new StringBuilder(len * 2);
for (int ii = 0; ii < len; ii++) {
char ch = str.charAt(ii);
switch (ch) {
case '\\':
case '"':
buf.append('\\').append(ch);
break;
case '\t':
buf.append('\\').append('t');
break;
case '\n':
buf.append('\\').append('n');
break;
case '\r':
buf.append('\\').append('r');
break;
case '\f':
buf.append('\\').append('f');
break;
default:
buf.append(ch);
break;
}
}
return buf.toString();
}
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
protected File _asroot;
}
@@ -0,0 +1,111 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.File;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
public class GenActionScriptStreamableTask extends GenActionScriptTask
{
@Override
protected void convert (File javaSource, Class<?> sclass, File outputLocation)
throws Exception
{
// Generate the current version of the streamable
StreamableClassRequirements reqs = new StreamableClassRequirements(sclass);
Set<String> imports = Sets.newLinkedHashSet();
imports.add(ObjectInputStream.class.getName());
imports.add(ObjectOutputStream.class.getName());
String extendsName = "";
if (!sclass.getSuperclass().equals(Object.class)) {
extendsName = addImportAndGetShortType(sclass.getSuperclass(), false, imports);
}
Set<String> implemented = Sets.newLinkedHashSet();
for (Class<?> iface : sclass.getInterfaces()) {
implemented.add(addImportAndGetShortType(iface, false, imports));
}
List<ASField> fields = Lists.newArrayList();
for (Field f : reqs.streamedFields) {
fields.add(new ASField(f, imports));
}
String output = mergeTemplate("com/threerings/presents/tools/streamable_as.tmpl",
"header", _header,
"package", sclass.getPackage().getName(),
"classname", sclass.getSimpleName(),
"imports", imports,
"extends", extendsName,
"implements", Joiner.on(", ").join(implemented),
"superclassStreamable", reqs.superclassStreamable,
"fields", fields);
if (outputLocation.exists()) {
// Merge in the previously generated version
String existing = Files.toString(outputLocation, Charsets.UTF_8);
output = new GeneratedSourceMerger().merge(output, existing);
} else if (!outputLocation.getParentFile().exists()) {
// Make sure the directory exists before trying to write there
Preconditions.checkArgument(outputLocation.getParentFile().mkdirs(),
"Unable to create directory to write '%s'", outputLocation.getAbsolutePath());
}
Files.write(output, outputLocation, Charsets.UTF_8);
}
protected static class ASField
{
public final String name;
public final String simpleType;
public final String reader;
public final String writer;
public ASField (Field f, Set<String> imports)
{
this.name = f.getName();
this.simpleType = addImportAndGetShortType(f.getType(), true, imports);
// Lists and Maps use their Streamers directly
if (List.class.isAssignableFrom(f.getType())) {
imports.add("com.threerings.io.streamers.ArrayStreamer");
} else if (Map.class.isAssignableFrom(f.getType())) {
imports.add("com.threerings.io.streamers.MapStreamer");
}
this.reader = toReadObject(f.getType());
this.writer = toWriteObject(f.getType(), name);
}
}
}
@@ -0,0 +1,374 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
import com.threerings.util.ActionScript;
import com.threerings.util.StreamableArrayList;
import com.threerings.util.StreamableHashMap;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.DObject;
/**
* Generates ActionScript versions of {@link Streamable} classes and provides routines used by the
* {@link GenDObjectTask} to create ActionScript versions of distributed objects.
*/
public class GenActionScriptTask extends GenTask
{
/**
* Configures the path to our ActionScript source files.
*/
public void setAsroot (File asroot)
{
_asroot = asroot;
}
/**
* Configures us with a header file that we'll prepend to all generated source files.
*/
public void setHeader (File header)
{
try {
_header = Files.toString(header, Charsets.UTF_8);
} catch (IOException ioe) {
System.err.println("Unabled to load header '" + header + ": " + ioe.getMessage());
}
}
/**
* Processes a resolved Streamable class instance.
*/
@Override
public void processClass (File source, Class<?> sclass)
throws Exception
{
// make sure we implement Streamable but don't extend DObject or InvocationMarshaller and
// that we're a class not an interface
if (!Streamable.class.isAssignableFrom(sclass) ||
DObject.class.isAssignableFrom(sclass) ||
InvocationMarshaller.class.isAssignableFrom(sclass) ||
((sclass.getModifiers() & Modifier.INTERFACE) != 0)) {
// System.err.println("Skipping " + sclass.getName() + "...");
return;
}
// if we have an ActionScript(omit=true) annotation, skip this class
Class<?> cclass = sclass;
do {
ActionScript asa = cclass.getAnnotation(ActionScript.class);
if (asa != null && asa.omit()) {
// System.err.println("Skipping " + sclass.getName() + "...");
return;
}
cclass = cclass.getSuperclass();
} while (cclass != null);
// determine the path to the corresponding action script source file
String path = toActionScriptType(sclass, false).replace(".", File.separator);
File asfile = new File(_asroot, path + ".as");
System.err.println("Converting " + sclass.getName() + "...");
convert(source, sclass, asfile);
}
protected void convert (File javaSource, Class<?> sclass, File output)
throws Exception
{
// parse the existing ActionScript source and generate what we don't
// have from the Java class
ActionScriptSource assrc = new ActionScriptSource(sclass);
assrc.absorbJava(javaSource);
assrc.imports.add(ObjectInputStream.class.getName());
assrc.imports.add(ObjectOutputStream.class.getName());
// see if our parent also implements Streamable
boolean needSuper = Streamable.class.isAssignableFrom(sclass.getSuperclass());
// add readObject() and writeObject() definitions
ActionScriptSource.Member member;
member = new ActionScriptSource.Member(
"readObject", (needSuper ? "override " : "") + READ_SIG);
member.noreplace = true;
member.comment = " // from interface Streamable\n";
StringBuilder body = new StringBuilder(" {\n");
if (needSuper) {
body.append(" super.readObject(ins);\n");
}
int added = 0;
for (Field field : sclass.getDeclaredFields()) {
if (!isStreamable(field)) {
continue;
}
body.append(" ");
body.append(field.getName()).append(" = ins.");
body.append(toReadObject(field.getType()));
body.append(";\n");
added++;
}
member.body = body.append(" }\n").toString();
if (added > 0) {
assrc.publicMethods.add(member);
}
member = new ActionScriptSource.Member(
"writeObject", (needSuper ? "override " : "") + WRITE_SIG);
member.noreplace = true;
member.comment = " // from interface Streamable\n";
body = new StringBuilder(" {\n");
if (needSuper) {
body.append(" super.writeObject(out);\n");
}
added = 0;
for (Field field : sclass.getDeclaredFields()) {
if (!isStreamable(field)) {
continue;
}
body.append(" out.");
body.append(toWriteObject(field.getType(), field.getName()));
body.append(";\n");
added++;
}
member.body = body.append(" }\n").toString();
if (added > 0) {
assrc.publicMethods.add(member);
}
// now we can parse existing definitions from any extant ActionScript source file
assrc.absorbActionScript(output);
// make sure our parent directory exists
output.getParentFile().mkdirs();
// now write all that out to the target source file
BufferedWriter out = new BufferedWriter(new FileWriter(output));
assrc.write(new PrintWriter(out));
}
protected boolean isStreamable (Field field)
{
int mods = field.getModifiers();
return !Modifier.isStatic(mods) && !Modifier.isTransient(mods);
}
protected static String addImportAndGetShortType (Class<?> type, boolean isField,
Set<String> imports)
{
String full = toActionScriptType(type, isField);
if (needsActionScriptImport(type, isField)) {
imports.add(full);
}
return Iterables.getLast(DOT_SPLITTER.split(full));
}
protected static boolean needsActionScriptImport (Class<?> type, boolean isField)
{
if (type.isArray()) {
return Byte.TYPE.equals(type.getComponentType()) || isField;
}
return (Long.TYPE.equals(type) || !type.isPrimitive()) && !String.class.equals(type);
}
/** Returns if the given class is an implementation of Map that doesn't know about Streaming */
protected static boolean isNaiveMap (Class<?> type)
{
return Map.class.isAssignableFrom(type) && !type.equals(StreamableHashMap.class);
}
/** Returns if the given class is an implementation of List that doesn't know about Streaming */
protected static boolean isNaiveList (Class<?> type)
{
return List.class.isAssignableFrom(type) && !type.equals(StreamableArrayList.class);
}
protected static String toActionScriptType (Class<?> type, boolean isField)
{
if (type.isArray() || isNaiveList(type)) {
if (Byte.TYPE.equals(type.getComponentType())) {
return "flash.utils.ByteArray";
}
if (isField) {
return "com.threerings.io.TypedArray";
}
return "Array";
} else if (isNaiveMap(type)) {
return "com.threerings.util.Map";
} else if (Integer.TYPE.equals(type) ||
Byte.TYPE.equals(type) ||
Short.TYPE.equals(type) ||
Character.TYPE.equals(type)) {
return "int";
} else if (Float.TYPE.equals(type) ||
Double.TYPE.equals(type)) {
return "Number";
} else if (Long.TYPE.equals(type)) {
return "com.threerings.util.Long";
} else if (Boolean.TYPE.equals(type)) {
return "Boolean";
} else {
// inner classes are not supported by ActionScript so we _
return type.getName().replaceAll("\\$", "_");
}
}
public static String toReadObject (Class<?> type)
{
if (type.equals(String.class)) {
return "readField(String)";
} else if (type.equals(Integer.class) ||
type.equals(Short.class) ||
type.equals(Byte.class)) {
String name = ActionScriptSource.toSimpleName(type.getName());
return "readField(" + name + ").value";
} else if (type.equals(Long.class)) {
String name = ActionScriptSource.toSimpleName(type.getName());
return "readField(" + name + ")";
} else if (type.equals(Boolean.TYPE)) {
return "readBoolean()";
} else if (type.equals(Byte.TYPE)) {
return "readByte()";
} else if (type.equals(Short.TYPE) || type.equals(Character.TYPE)) {
return "readShort()";
} else if (type.equals(Integer.TYPE)) {
return "readInt()";
} else if (type.equals(Long.TYPE)) {
return "readLong()";
} else if (type.equals(Float.TYPE)) {
return "readFloat()";
} else if (type.equals(Double.TYPE)) {
return "readDouble()";
} else if (isNaiveMap(type)) {
return "readField(MapStreamer.INSTANCE)";
} else if (isNaiveList(type)) {
return "readField(ArrayStreamer.INSTANCE)";
} else if (type.isArray()) {
if (!type.getComponentType().isPrimitive()) {
return "readObject(TypedArray)";
} else {
if (Double.TYPE.equals(type.getComponentType())) {
return "readField(TypedArray.getJavaType(Number))";
} else if (Boolean.TYPE.equals(type.getComponentType())) {
return "readField(TypedArray.getJavaType(Boolean))";
} else if (Integer.TYPE.equals(type.getComponentType())) {
return "readField(TypedArray.getJavaType(int))";
} else if (Byte.TYPE.equals(type.getComponentType())) {
return "readField(ByteArray)";
} else {
throw new IllegalArgumentException(type
+ " isn't supported to stream to actionscript");
}
}
} else {
return "readObject(" + Iterables.getLast(DOT_SPLITTER.split(toActionScriptType(type, false))) + ")";
}
}
public static String toWriteObject (Class<?> type, String name)
{
if (type.equals(Integer.class)) {
return "writeObject(new Integer(" + name + "))";
} else if (type.equals(Boolean.TYPE)) {
return "writeBoolean(" + name + ")";
} else if (type.equals(Byte.TYPE)) {
return "writeByte(" + name + ")";
} else if (type.equals(Short.TYPE) ||
type.equals(Character.TYPE)) {
return "writeShort(" + name + ")";
} else if (type.equals(Integer.TYPE)) {
return "writeInt(" + name + ")";
} else if (type.equals(Long.TYPE)) {
return "writeLong(" + name + ")";
} else if (type.equals(Float.TYPE)) {
return "writeFloat(" + name + ")";
} else if (type.equals(Double.TYPE)) {
return "writeDouble(" + name + ")";
} else if (type.equals(Long.class) ||
type.equals(String.class) ||
(type.isArray() && type.getComponentType().isPrimitive())) {
return "writeField(" + name + ")";
} else if (isNaiveList(type)) {
return "writeField(" + name + ", ArrayStreamer.INSTANCE)";
} else if (isNaiveMap(type)) {
return "writeField(" + name + ", MapStreamer.INSTANCE)";
} else {
return "writeObject(" + name + ")";
}
}
/** A header to put on all generated source files. */
protected String _header;
/** The path to our ActionScript source files. */
protected File _asroot;
protected static final String READ_SIG =
"public function readObject (ins :ObjectInputStream) :void";
protected static final String WRITE_SIG =
"public function writeObject (out :ObjectOutputStream) :void";
protected static final Splitter DOT_SPLITTER = Splitter.on('.');
}
@@ -0,0 +1,188 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.io.File;
import com.google.common.collect.Lists;
import com.samskivert.util.StringUtil;
import com.threerings.presents.annotation.TransportHint;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.OidList;
/**
* Generates necessary additional distributed object declarations and methods.
*/
public class GenDObjectTask extends GenTask
{
@Override
public void execute ()
{
// resolve the DObject class using our classloader
_doclass = loadClass(DObject.class.getName());
_dsclass = loadClass(DSet.class.getName());
_olclass = loadClass(OidList.class.getName());
super.execute();
}
/** Processes a resolved distributed object class instance. */
@Override
public void processClass (File source, Class<?> oclass)
throws Exception
{
// make sure we extend distributed object
if (!_doclass.isAssignableFrom(oclass) || _doclass.equals(oclass)) {
// System.err.println("Skipping " + oclass.getName() + "...");
return;
}
// determine which fields we need to deal with
ArrayList<Field> flist = Lists.newArrayList();
Field[] fields = oclass.getDeclaredFields();
for (Field f : fields) {
int mods = f.getModifiers();
if (!Modifier.isPublic(mods) ||
Modifier.isStatic(mods) ||
Modifier.isTransient(mods)) {
continue;
}
flist.add(f);
}
// slurp our source file into newline separated strings
SourceFile sfile = new SourceFile();
sfile.readFrom(source);
// generate our fields section and our methods section
StringBuilder fsection = new StringBuilder();
StringBuilder msection = new StringBuilder();
for (int ii = 0; ii < flist.size(); ii++) {
Field f = flist.get(ii);
Class<?> ftype = f.getType();
String fname = f.getName();
// create a map to hold our template data
Map<String, Object> data = new HashMap<String, Object>();
data.put("field", fname);
data.put("generated", GenUtil.getGeneratedAnnotation(getClass(), 4, false));
data.put("type", GenUtil.simpleName(f));
data.put("wrapfield", GenUtil.boxArgument(ftype, "value"));
data.put("wrapofield", GenUtil.boxArgument(ftype, "ovalue"));
data.put("clonefield", GenUtil.cloneArgument(_dsclass, f, "value"));
data.put("capfield", StringUtil.unStudlyName(fname).toUpperCase());
data.put("upfield", StringUtil.capitalize(fname));
// determine the type of transport
TransportHint hint = f.getAnnotation(TransportHint.class);
if (hint == null) {
// inherit hint from class annotation
hint = f.getDeclaringClass().getAnnotation(TransportHint.class);
}
String transport;
if (hint == null) {
transport = "";
} else {
transport = ",\n" +
" com.threerings.presents.net.Transport.getInstance(\n" +
" com.threerings.presents.net.Transport.Type." +
hint.type().name() + ", " + hint.channel() + ")";
}
data.put("transport", transport);
// if this field is an array, we need its component types
if (ftype.isArray()) {
Class<?> etype = ftype.getComponentType();
data.put("have_elem", true);
data.put("elemtype", GenUtil.simpleName(etype));
data.put("wrapelem", GenUtil.boxArgument(etype, "value"));
data.put("wrapoelem", GenUtil.boxArgument(etype, "ovalue"));
}
// if this field is a generic DSet, we need its bound type
if (_dsclass.isAssignableFrom(ftype)) {
Type t = f.getGenericType();
// we need to walk up the heirarchy until we get to the parameterized DSet
while (t instanceof Class<?>) {
t = ((Class<?>)t).getGenericSuperclass();
}
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)t;
if (pt.getActualTypeArguments().length > 0) {
data.put("etype", GenUtil.simpleName(pt.getActualTypeArguments()[0]));
}
} else {
data.put("etype", "DSet.Entry");
}
}
// now figure out which template to use
String tname = "field.tmpl";
if (_dsclass.isAssignableFrom(ftype)) {
tname = "set.tmpl";
} else if (_olclass.isAssignableFrom(ftype)) {
tname = "oidlist.tmpl";
}
// append the merged templates as appropriate to the string buffers
if (ii > 0) {
fsection.append("\n");
msection.append("\n");
}
fsection.append(mergeTemplate(NAME_TMPL, data));
msection.append(mergeTemplate(BASE_TMPL + tname, data));
}
// now bolt everything back together into a class declaration
sfile.writeTo(source, fsection.toString(), msection.toString());
}
/** {@link DObject} resolved with the proper classloader so that we
* can compare it to loaded derived classes. */
protected Class<?> _doclass;
/** {@link DSet} resolved with the proper classloader so that we can
* compare it to loaded derived classes. */
protected Class<?> _dsclass;
/** {@link OidList} resolved with the proper classloader so that we
* can compare it to loaded derived classes. */
protected Class<?> _olclass;
/** Specifies the start of the path to our various templates. */
protected static final String BASE_TMPL =
"com/threerings/presents/tools/dobject_";
/** Specifies the path to the name code template. */
protected static final String NAME_TMPL = BASE_TMPL + "name.tmpl";
}
@@ -0,0 +1,154 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.io.File;
import com.google.common.collect.Iterators;
import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.StringUtil;
import com.threerings.presents.client.InvocationDecoder;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationSender;
/**
* An Ant task for generating invocation receiver marshalling and unmarshalling classes.
*/
public class GenReceiverTask extends InvocationTask
{
@Override
public void processClass (File source, Class<?> receiver)
throws Exception
{
System.out.println("Processing " + receiver.getName() + "...");
String rname = receiver.getName();
String rpackage = "";
int didx = rname.lastIndexOf(".");
if (didx != -1) {
rpackage = rname.substring(0, didx);
rname = rname.substring(didx+1);
}
// verify that the receiver class name is as we expect it to be
if (!rname.endsWith("Receiver")) {
System.err.println("Cannot process '" + rname + "':");
System.err.println("Receiver classes must be named SomethingReceiver.");
return;
}
ImportSet imports = new ImportSet();
ComparableArrayList<ServiceMethod> methods = new ComparableArrayList<ServiceMethod>();
// look through and locate our receiver methods
Method[] methdecls = receiver.getDeclaredMethods();
for (Method m : methdecls) {
// receiver interface methods must be public and abstract
if (!Modifier.isPublic(m.getModifiers()) &&
!Modifier.isAbstract(m.getModifiers())) {
continue;
}
methods.add(new ServiceMethod(m, imports));
}
methods.sort();
// import Foo instead of Foo$Bar
imports.swapInnerClassesForParents();
// Adjust any bits that want to import arrays to instead import their element classes.
imports.translateClassArrays();
imports.removeArrays();
// get rid of primitives and java.lang types
imports.removeGlobals();
generateSender(source, rname, rpackage, methods, imports.toList().iterator());
generateDecoder(receiver, source, rname, rpackage, methods, imports.toList().iterator(),
StringUtil.md5hex(rpackage + "." + rname));
}
protected void generateSender (File source, String rname, String rpackage,
List<?> methods, Iterator<String> imports)
throws Exception
{
String name = rname.replace("Receiver", "");
String spackage = rpackage.replace(".client", ".server");
// construct our imports list
ComparableArrayList<String> implist = new ComparableArrayList<String>();
Iterators.addAll(implist, imports);
checkedAdd(implist, ClientObject.class.getName());
checkedAdd(implist, InvocationSender.class.getName());
String dname = rname.replace("Receiver", "Decoder");
checkedAdd(implist, rpackage + "." + dname);
checkedAdd(implist, rpackage + "." + rname);
implist.sort();
// determine the path to our sender file
String mpath = source.getPath();
mpath = mpath.replace("Receiver", "Sender");
mpath = replacePath(mpath, "/client/", "/server/");
writeFile(mpath, mergeTemplate(SENDER_TMPL,
"name", name,
"package", spackage,
"methods", methods,
"imports", implist));
}
protected void generateDecoder (Class<?> receiver, File source, String rname, String rpackage,
List<ServiceMethod> methods, Iterator<String> imports,
String rcode) throws Exception
{
String name = rname.replace("Receiver", "");
// construct our imports list
ComparableArrayList<String> implist = new ComparableArrayList<String>();
Iterators.addAll(implist, imports);
checkedAdd(implist, InvocationDecoder.class.getName());
implist.sort();
// determine the path to our sender file
String mpath = source.getPath();
mpath = mpath.replace("Receiver", "Decoder");
writeFile(mpath, mergeTemplate(DECODER_TMPL,
"name", name,
"receiver_code", rcode,
"package", rpackage,
"methods", methods,
"imports", implist));
}
/** Specifies the path to the sender template. */
protected static final String SENDER_TMPL =
"com/threerings/presents/tools/sender.tmpl";
/** Specifies the path to the decoder template. */
protected static final String DECODER_TMPL =
"com/threerings/presents/tools/decoder.tmpl";
}
@@ -0,0 +1,720 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.File;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.samskivert.util.StringUtil;
import com.threerings.util.ActionScript;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
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;
import com.threerings.presents.server.InvocationProvider;
/**
* An Ant task for generating invocation service marshalling and
* unmarshalling classes.
* TODO: when generating the imports for exported action script files, there are just enough
* conversions of primitive types (e.g. float -> Number), array types (e.g. int[] -> TypedArray)
* and three rings utility types (e.g. float -> Float) to make the existing serivces work. It
* should be possible to create a complete list of these conversions so that future services
* can be generated without problems.
*/
public class GenServiceTask extends InvocationTask
{
/** Used to keep track of custom InvocationListener derivations. */
public class ServiceListener implements Comparable<ServiceListener>
{
public Class<?> listener;
public List<ServiceMethod> methods = Lists.newArrayList();
/** Contains all imports required for the parameters of the methods in this listener. */
public ImportSet imports = new ImportSet();
public ServiceListener (Class<?> service, Class<?> listener)
{
this.listener = listener;
// compute the union of all InvocationListener extensions implemented by this interface
Set<Class<?>> ifaces = Sets.newHashSet();
addInterfaces(listener, ifaces);
// add method marshallers for all methods in all interfaces (the marshaller will not
// extend the marshallers for its parent interfaces and will use its own codes)
for (Class<?> iface : ifaces) {
Method[] methdecls = iface.getDeclaredMethods();
for (Method m : methdecls) {
// service interface methods must be public and abstract
if (!Modifier.isPublic(m.getModifiers()) &&
!Modifier.isAbstract(m.getModifiers())) {
continue;
}
if (_verbose) {
System.out.println("Adding " + m + ", imports are " +
StringUtil.toString(imports));
}
methods.add(new ServiceMethod(m, imports));
if (_verbose) {
System.out.println("Added " + m + ", imports are " +
StringUtil.toString(imports));
}
}
}
Collections.sort(methods);
}
protected void addInterfaces (Class<?> listener, Set<Class<?>> ifaces)
{
if (!_ilistener.isAssignableFrom(listener) || _ilistener.equals(listener)) {
return;
}
ifaces.add(listener);
for (Class<?> iface : listener.getInterfaces()) {
addInterfaces(iface, ifaces);
}
}
/**
* Checks whether any of our methods have parameterized types.
*/
public boolean hasParameterizedMethodArgs ()
{
return Iterables.any(methods, new Predicate<ServiceMethod>() {
public boolean apply (ServiceMethod sm) {
return sm.hasParameterizedArgs();
}
});
}
public String getListenerName ()
{
String name = GenUtil.simpleName(listener);
name = name.replace("Listener", "");
int didx = name.indexOf(".");
return name.substring(didx+1);
}
public String adapterCtorArgs () {
StringBuilder sb = new StringBuilder();
for (ServiceMethod m : methods) {
sb.append(m.method.getName() + " :Function, ");
}
return sb.toString();
}
// from interface Comparable<ServiceListener>
public int compareTo (ServiceListener other)
{
return getListenerName().compareTo(other.getListenerName());
}
@Override
public boolean equals (Object other)
{
return (other != null) && getClass().equals(other.getClass()) &&
listener.equals(((ServiceListener)other).listener);
}
@Override
public int hashCode ()
{
return listener.getName().hashCode();
}
}
/** Used to track services for which we should not generate a provider interface. */
public class Providerless
{
public void setService (String className)
{
_providerless.add(className);
}
}
/** Used to track services for which we should create listener adapters in actionscript. */
public class Adapter
{
public void setService (String className)
{
_aslistenerAdapters.add(className);
}
}
/**
* Configures the path to our ActionScript source files.
*/
public void setAsroot (File asroot)
{
_asroot = asroot;
}
public Providerless createProviderless ()
{
return new Providerless();
}
public Adapter createAdapter ()
{
return new Adapter();
}
// documentation inherited
@Override
public void processClass (File source, Class<?> service)
throws Exception
{
System.out.println("Processing " + service.getName() + "...");
// verify that the service class name is as we expect it to be
if (!service.getName().endsWith("Service")) {
System.err.println("Cannot process '" + service.getName() + "':");
System.err.println("Service classes must be named SomethingService.");
return;
}
ServiceDescription desc = new ServiceDescription(service);
generateMarshaller(source, desc);
generateDispatcher(source, desc);
if (!_providerless.contains(service.getSimpleName())) {
generateProvider(source, desc);
}
}
protected void generateMarshaller (File source, ServiceDescription sdesc)
throws Exception
{
if (_verbose) {
System.out.println("Generating marshaller");
}
String sname = sdesc.sname;
String name = sname.replace("Service", "");
String mname = sname.replace("Service", "Marshaller");
String mpackage = sdesc.spackage.replace(".client", ".data");
// ----------- Part I - java marshaller
// start with all imports (service methods and listener methods)
ImportSet imports = sdesc.constructAllImports();
// import things marshaller will always need
imports.add(sdesc.service);
imports.add(Client.class);
imports.add(InvocationMarshaller.class);
// if any listeners are to be present, they need the response event
if (sdesc.listeners.size() > 0) {
imports.add(InvocationResponseEvent.class);
}
// import classes contained in arrays
imports.translateClassArrays();
// get rid of java.lang stuff and primitives
imports.removeGlobals();
// get rid of all arrays (they are automatic in java)
imports.removeArrays();
// for each listener type, also import the corresponding marshaller
imports.duplicateAndMunge("*Listener",
"Service", "Marshaller",
"Listener", "Marshaller",
".client.", ".data.");
// import the parent class of Foo$Bar
imports.swapInnerClassesForParents();
// remove imports in our own package
imports.removeSamePackage(mpackage);
Map<String, Object> ctx = new HashMap<String, Object>();
ctx.put("name", name);
ctx.put("generated", getGeneratedAnnotation(name));
ctx.put("package", mpackage);
ctx.put("methods", sdesc.methods);
ctx.put("listeners", sdesc.listeners);
ctx.put("imports", imports.toList());
// determine the path to our marshaller file
String mpath = source.getPath();
mpath = mpath.replace("Service", "Marshaller");
mpath = replacePath(mpath, "/client/", "/data/");
writeFile(mpath, mergeTemplate(MARSHALLER_TMPL, ctx));
// if we're not configured with an ActionScript source root, don't generate the
// ActionScript versions
if (_asroot == null || sdesc.skipAS) {
return;
}
// ----------- Part II - as marshaller
// start with the service method imports
imports = sdesc.imports.clone();
// add some things that marshallers just need
imports.add(sdesc.service);
imports.add(Client.class);
imports.add(InvocationMarshaller.class);
// replace inner classes with action script equivalents
imports.translateInnerClasses();
// replace primitive types with OOO types (required for unboxing)
imports.replace("byte", "com.threerings.util.Byte");
imports.replace("int", "com.threerings.util.Integer");
imports.replace("boolean", "com.threerings.util.langBoolean");
imports.replace("[B", "flash.utils.ByteArray");
imports.replace("float", "com.threerings.util.Float");
imports.replace("[I", "com.threerings.io.TypedArray");
// ye olde special case - any method that uses a default listener
// causes the need for the default listener marshaller
imports.duplicateAndMunge("*.InvocationService_InvocationListener",
"InvocationService_InvocationListener",
"InvocationMarshaller_ListenerMarshaller",
".client.", ".data.");
// any use of a listener requires the listener marshaller
imports.pushOut("*.InvocationService_InvocationListener");
imports.duplicateAndMunge("*Listener",
"Service", "Marshaller",
"Listener", "Marshaller",
".client.", ".data.");
imports.popIn();
// convert java primitive types to ooo util types
imports.replace(Integer.class, "com.threerings.util.Integer");
// get rid of java.lang stuff and any remaining primitives
imports.removeGlobals();
if (imports.removeAll("[L*") > 0) {
imports.add("com.threerings.io.TypedArray");
}
// get rid of remaining arrays
imports.removeArrays();
// remove imports in our own package
imports.removeSamePackage(mpackage);
ctx.put("imports", imports.toList());
// now generate ActionScript versions of our marshaller
// 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";
writeFile(ampath, mergeTemplate(AS_MARSHALLER_TMPL, ctx));
// ----------- Part III - as listener marshallers
Class<?> imlm = InvocationMarshaller.ListenerMarshaller.class;
// now generate ActionScript versions of our listener marshallers
// because those have to be in separate files
for (ServiceListener listener : sdesc.listeners) {
// start imports with just those used by listener methods
imports = listener.imports.clone();
// always need the super class and the listener class
imports.add(imlm);
imports.add(listener.listener);
// replace '$' with '_' for action script naming convention
imports.translateInnerClasses();
// convert primitive java types to ooo util types
imports.replace("long", "com.threerings.util.Long");
// convert object arrays to typed arrays
if (imports.removeAll("[L*") > 0) {
imports.add("com.threerings.io.TypedArray");
}
// get rid of remaining primitives and java.lang types
imports.removeGlobals();
// remove imports in our own package
imports.removeSamePackage(mpackage);
ctx.put("imports", imports.toList());
ctx.put("listener", listener);
String aslpath = _asroot + File.separator + mppath +
File.separator + mname + "_" + listener.getListenerName() + "Marshaller.as";
writeFile(aslpath, mergeTemplate(AS_LISTENER_MARSHALLER_TMPL, ctx));
}
// ----------- Part IV - as service
// then make some changes to the context and generate ActionScript
// versions of the service interface itself
// start with the service methods' imports
imports = sdesc.imports.clone();
// add some things required by action script
imports.add(Client.class);
imports.add(InvocationService.class);
// allow primitive types in service methods
imports.replace("[B", "flash.utils.ByteArray");
imports.replace("[I", "com.threerings.io.TypedArray");
// convert java primitive types to ooo util types
imports.replace("java.lang.Integer", "com.threerings.util.Integer");
if (imports.removeAll("[L*") > 0) {
imports.add("com.threerings.io.TypedArray");
}
// get rid of primitives and java.lang classes
imports.removeGlobals();
// get rid of remaining arrays
imports.removeArrays();
// change imports of Foo$Bar to Foo_Bar
imports.translateInnerClasses();
// remove imports in our own package
imports.removeSamePackage(sdesc.spackage);
ctx.put("imports", imports.toList());
ctx.put("package", sdesc.spackage);
// make sure our service directory exists
String sppath = sdesc.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";
writeFile(aspath, mergeTemplate(AS_SERVICE_TMPL, ctx));
// ----------- Part V - as service listeners
Class<?> isil = InvocationService.InvocationListener.class;
// also generate ActionScript versions of any inner listener
// interfaces because those have to be in separate files
for (ServiceListener listener : sdesc.listeners) {
// start with just the imports needed by listener methods
imports = listener.imports.clone();
// add things needed by all listeners
imports.add(isil);
imports.add(listener.listener);
// change Foo$Bar to Foo_Bar
imports.translateInnerClasses();
// use a typed array for any arrays of objects
if (imports.removeAll("[L*") > 0) {
imports.add("com.threerings.io.TypedArray");
}
// convert java primitive types to ooo util types
imports.replace("long", "com.threerings.util.Long");
// get rid of remaining primitives and java.lang types
imports.removeGlobals();
// remove imports in our own package
imports.removeSamePackage(sdesc.spackage);
ctx.put("imports", imports.toList());
ctx.put("listener", listener);
String aslpath = _asroot + File.separator + sppath + File.separator +
sname + "_" + listener.getListenerName() + "Listener.as";
writeFile(aslpath, mergeTemplate(AS_LISTENER_SERVICE_TMPL, ctx));
if (_aslistenerAdapters.contains(sname)) {
String aslapath = _asroot + File.separator + sppath + File.separator +
sname + "_" + listener.getListenerName() + "ListenerAdapter.as";
writeFile(aslapath, mergeTemplate(AS_LISTENER_ADAPTER_SERVICE_TMPL, ctx));
}
}
}
protected void generateDispatcher (File source, ServiceDescription sdesc)
throws Exception
{
if (_verbose) {
System.out.println("Generating dispatcher");
}
String name = sdesc.sname.replace("Service", "");
String dpackage = sdesc.spackage.replace(".client", ".server");
// start with the imports required by service methods
ImportSet imports = sdesc.imports.clone();
// If any listeners are to be used in dispatches, we need to import the service
if (sdesc.listeners.size() > 0) {
imports.add(sdesc.service);
}
// swap Client for ClientObject
imports.add(ClientObject.class);
imports.remove(Client.class);
// add some classes required for all dispatchers
imports.add(InvocationDispatcher.class);
imports.add(InvocationException.class);
// import classes contained in arrays
imports.translateClassArrays();
// get rid of primitives and java.lang types
imports.removeGlobals();
// get rid of arrays
imports.removeArrays();
// import the Marshaller corresponding to the service
imports.addMunged(sdesc.service,
"Service", "Marshaller",
".client.", ".data.");
// import Foo instead of Foo$Bar
imports.swapInnerClassesForParents();
// remove imports in our own package
imports.removeSamePackage(dpackage);
// determine the path to our marshaller file
String mpath = source.getPath();
mpath = mpath.replace("Service", "Dispatcher");
mpath = replacePath(mpath, "/client/", "/server/");
writeFile(mpath, mergeTemplate(DISPATCHER_TMPL,
"name", name,
"generated", getGeneratedAnnotation(name),
"package", dpackage,
"methods", sdesc.methods,
"imports", imports.toList()));
}
protected void generateProvider (File source, ServiceDescription sdesc)
throws Exception
{
if (_verbose) {
System.out.println("Generating provider");
}
String name = sdesc.sname.replace("Service", "");
String mpackage = sdesc.spackage.replace(".client", ".server");
// start with imports required by service methods
ImportSet imports = sdesc.imports.clone();
// swap Client for ClientObject
imports.add(ClientObject.class);
imports.remove(Client.class);
// import superclass and service
imports.add(InvocationProvider.class);
imports.add(sdesc.service);
// any method that takes a listener may throw this
if (sdesc.hasAnyListenerArgs()) {
imports.add(InvocationException.class);
}
// import classes contained in arrays
imports.translateClassArrays();
// get rid of primitives and java.lang types
imports.removeGlobals();
// get rid of arrays
imports.removeArrays();
// import Foo instead of Foo$Bar
imports.swapInnerClassesForParents();
// remove imports in our own package
imports.removeSamePackage(mpackage);
// determine the path to our provider file
String mpath = source.getPath();
mpath = mpath.replace("Service", "Provider");
mpath = replacePath(mpath, "/client/", "/server/");
writeFile(mpath, mergeTemplate(PROVIDER_TMPL,
"name", name,
"generated", getGeneratedAnnotation(name),
"package", mpackage,
"methods", sdesc.methods,
"listeners", sdesc.listeners,
"imports", imports.toList()));
}
/**
* Helper to get the appropriate "@Generated" annotation for service classes.
*/
protected String getGeneratedAnnotation (String name)
{
return GenUtil.getGeneratedAnnotation(getClass(), 0, false,
"Derived from " + name + "Service.java.");
}
/** Rolls up everything needed for the generate* methods. */
protected class ServiceDescription
{
public Class<?> service;
public String sname;
public String spackage;
public ImportSet imports = new ImportSet();
public List<ServiceMethod> methods = Lists.newArrayList();
public List<ServiceListener> listeners = Lists.newArrayList();
public final boolean skipAS;
public ServiceDescription (Class<?> serviceClass)
{
service = serviceClass;
sname = service.getSimpleName();
spackage = service.getPackage().getName();
ActionScript asa = service.getAnnotation(ActionScript.class);
skipAS = (asa != null) && asa.omit();
// look through and locate our service methods, also locating any
// custom InvocationListener derivations along the way
Method[] methdecls = service.getDeclaredMethods();
for (Method m : methdecls) {
// 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 (Class<?> arg : args) {
if (_ilistener.isAssignableFrom(arg) &&
GenUtil.simpleName(arg).startsWith(sname + ".")) {
checkedAdd(listeners, new ServiceListener(service, arg));
}
}
if (_verbose) {
System.out.println("Adding " + m + ", imports are " +
StringUtil.toString(imports));
}
methods.add(new ServiceMethod(m, imports));
if (_verbose) {
System.out.println("Added " + m + ", imports are " +
StringUtil.toString(imports));
}
}
Collections.sort(listeners);
Collections.sort(methods);
}
/**
* Checks if any of the service method arguments are listener types.
*/
public boolean hasAnyListenerArgs ()
{
return Iterables.any(methods, new Predicate<ServiceMethod>() {
public boolean apply (ServiceMethod sm) {
return !sm.listenerArgs.isEmpty();
}
});
}
/**
* Constructs a union of the imports of the service methods and all listener methods.
*/
public ImportSet constructAllImports ()
{
ImportSet allimports = imports.clone();
for (ServiceListener listener : listeners) {
allimports.addAll(listener.imports);
}
return allimports;
}
}
/** The path to our ActionScript source files. */
protected File _asroot;
/** Services for which we should not generate provider interfaces. */
protected Set<String> _providerless = Sets.newHashSet();
/** Services for which we should generate actionscript listener adapters. */
protected Set<String> _aslistenerAdapters = Sets.newHashSet();
/** 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";
/** 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 listener adapter service template. */
protected static final String AS_LISTENER_ADAPTER_SERVICE_TMPL =
"com/threerings/presents/tools/service_listener_adapter_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";
}
@@ -0,0 +1,231 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.io.File;
import java.io.IOException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import com.google.common.collect.Lists;
import com.threerings.io.Streamable;
import com.threerings.presents.dobj.DObject;
/**
* Generates <code>readObject()</code> and <code>writeObject()</code> methods for {@link
* Streamable} classes that have protected or private members so that they can be used in a
* sandboxed environment.
*/
public class GenStreamableTask extends Task
{
/**
* Adds a nested &lt;fileset&gt; element which enumerates streamable source files.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
@Override
public void execute ()
{
for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (String srcFile : srcFiles) {
processClass(new File(fromDir, srcFile));
}
}
}
/**
* Processes a {@link Streamable} source file.
*/
protected void processClass (File source)
{
// load up the file and determine it's package and classname
String name = null;
try {
name = GenUtil.readClassName(source);
} catch (Exception e) {
System.err.println("Failed to parse " + source + ": " + e.getMessage());
return;
}
System.err.println("Considering " + name + "...");
try {
// in order for annotations to work, this task and all the classes it uses must be
// loaded from the same class loader as the classes on which we are going to
// introspect; this is non-ideal but unavoidable
processClass(source, getClass().getClassLoader().loadClass(name));
} catch (ClassNotFoundException cnfe) {
System.err.println("Failed to load " + name + ".\nMissing class: " + cnfe.getMessage());
System.err.println("Be sure to set the 'classpathref' attribute to a classpath\n" +
"that contains your projects invocation service classes.");
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
/**
* Processes a resolved {@link Streamable} class instance.
*/
protected void processClass (File source, Class<?> sclass)
throws IOException
{
StreamableClassRequirements reqs = new StreamableClassRequirements(sclass);
// we must implement Streamable, not be a DObject and have some fields that need to be
// streamed
if (!Streamable.class.isAssignableFrom(sclass) || DObject.class.isAssignableFrom(sclass) ||
reqs.streamedFields.isEmpty()) {
// System.err.println("Skipping " + sclass.getName() + "...");
return;
}
// add readObject() and writeObject() definitions
StringBuilder readbuf = new StringBuilder(READ_OPEN);
StringBuilder writebuf = new StringBuilder(WRITE_OPEN);
if (reqs.superclassStreamable) {
readbuf.append(" super.readObject(ins);\n");
writebuf.append(" super.writeObject(out);\n");
}
for (Field field : reqs.streamedFields) {
readbuf.append(" ");
readbuf.append(field.getName()).append(" = ");
readbuf.append(toReadObject(field));
readbuf.append(";\n");
writebuf.append(" out.");
writebuf.append(toWriteObject(field));
writebuf.append(";\n");
}
readbuf.append(READ_CLOSE);
writebuf.append(WRITE_CLOSE);
SourceFile sfile = new SourceFile();
try {
sfile.readFrom(source);
} catch (IOException ioe) {
System.err.println("Error reading " + source + ": " + ioe);
}
// don't overwrite an existing readObject() or writeObject()
StringBuilder methods = new StringBuilder();
if (!sfile.containsString("public void readObject")) {
methods.append(readbuf);
}
if (!sfile.containsString("public void writeObject")) {
if (methods.length() > 0) {
methods.append("\n");
}
methods.append(writebuf);
}
if (methods.length() == 0) {
return; // nothing to do
}
System.err.println("Converting " + sclass.getName() + "...");
try {
sfile.writeTo(source, null, methods.toString());
} catch (IOException ioe) {
System.err.println("Error writing " + source + ": " + ioe);
}
}
protected String toReadObject (Field field)
{
Class<?> type = field.getType();
if (type.equals(String.class)) {
return "ins.readUTF()";
} else if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) {
return "ins.readBoolean()";
} else if (type.equals(Byte.TYPE) || type.equals(Byte.class)) {
return "ins.readByte()";
} else if (type.equals(Short.TYPE) || type.equals(Short.class)) {
return "ins.readShort()";
} else if (type.equals(Integer.TYPE) || type.equals(Integer.class)) {
return "ins.readInt()";
} else if (type.equals(Long.TYPE) || type.equals(Long.class)) {
return "ins.readLong()";
} else if (type.equals(Float.TYPE) || type.equals(Float.class)) {
return "ins.readFloat()";
} else if (type.equals(Double.TYPE) || type.equals(Double.class)) {
return "ins.readDouble()";
} else {
return "(" + GenUtil.simpleName(field) + ")ins.readObject()";
}
}
protected String toWriteObject (Field field)
{
Class<?> type = field.getType();
String name = field.getName();
if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) {
return "writeBoolean(" + name + ")";
} else if (type.equals(Byte.TYPE) || type.equals(Byte.class)) {
return "writeByte(" + name + ")";
} else if (type.equals(Short.TYPE) || type.equals(Short.class)) {
return "writeShort(" + name + ")";
} else if (type.equals(Integer.TYPE) || type.equals(Integer.class)) {
return "writeInt(" + name + ")";
} else if (type.equals(Long.TYPE) || type.equals(Long.class)) {
return "writeLong(" + name + ")";
} else if (type.equals(Float.TYPE) || type.equals(Float.class)) {
return "writeFloat(" + name + ")";
} else if (type.equals(Double.TYPE) || type.equals(Double.class)) {
return "writeDouble(" + name + ")";
} else if (type.equals(String.class)) {
return "writeUTF(" + name + ")";
} else {
return "writeObject(" + name + ")";
}
}
/** A list of filesets that contain tile images. */
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
protected static final String READ_OPEN =
" // from interface Streamable\n" +
" public void readObject (ObjectInputStream ins)\n" +
" throws IOException, ClassNotFoundException\n" +
" {\n";
protected static final String READ_CLOSE = " }\n";
protected static final String WRITE_OPEN =
" // from interface Streamable\n" +
" public void writeObject (ObjectOutputStream out)\n" +
" throws IOException\n" +
" {\n";
protected static final String WRITE_CLOSE = " }\n";
}
@@ -0,0 +1,163 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.List;
import java.util.Map;
import java.io.File;
import java.io.InputStreamReader;
import org.apache.tools.ant.AntClassLoader;
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 com.google.common.collect.Maps;
import com.google.common.collect.Lists;
import com.samskivert.mustache.Mustache;
public abstract class GenTask extends Task
{
/**
* Adds a nested &lt;fileset&gt; element which enumerates service declaration source files.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Configures to output extra information when generating code.
*/
public void setVerbose (boolean verbose)
{
_verbose = verbose;
}
/** Configures our classpath which we'll use to load service classes. */
public void setClasspathref (Reference pathref)
{
_cloader = ClasspathUtils.getClassLoaderForPath(getProject(), pathref);
// set the parent of the classloader to be the classloader used to load this task, rather
// than the classloader used to load Ant, so that we have access to Narya classes like
// TransportHint
((AntClassLoader)_cloader).setParent(getClass().getClassLoader());
}
/**
* Performs the actual work of the task.
*/
@Override
public void execute ()
{
for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (String srcFile : srcFiles) {
File source = new File(fromDir, srcFile);
try {
processClass(source, loadClass(source));
} catch (Exception e) {
throw new BuildException(e);
}
}
}
}
/**
* Merges the specified template using the supplied mapping of keys to objects.
*
* @param data a series of key, value pairs where the keys must be strings and the values can
* be any object.
*/
protected String mergeTemplate (String template, Object... data)
throws Exception
{
Map<String, Object> ctx = Maps.newHashMap();
for (int ii = 0; ii < data.length; ii += 2) {
ctx.put((String)data[ii], data[ii+1]);
}
return mergeTemplate(template, ctx);
}
/**
* Merges the specified template using the supplied mapping of string keys to objects.
*
* @return a string containing the merged text.
*/
protected String mergeTemplate (String template, Map<String, Object> data)
throws Exception
{
return Mustache.compiler().escapeHTML(false).compile(new InputStreamReader(
getClass().getClassLoader().getResourceAsStream(template), "UTF-8")).execute(data);
}
/**
* Process a class found from the given source file that was on the filesets given to this
* task.
*/
protected abstract void processClass (File source, Class<?> klass)
throws Exception;
protected Class<?> loadClass (File source)
{
// load up the file and determine it's package and classname
String name;
try {
name = GenUtil.readClassName(source);
} catch (Exception e) {
throw new BuildException("Failed to parse " + source + ": " + e.getMessage());
}
return loadClass(name);
}
protected Class<?> loadClass (String name)
{
if (_cloader == null) {
throw new BuildException("This task requires a 'classpathref' attribute " +
"to be set to the project's classpath.");
}
try {
return _cloader.loadClass(name);
} catch (ClassNotFoundException cnfe) {
throw new BuildException(
"Failed to load " + name + ". Be sure to set the 'classpathref' attribute to a " +
"classpath that contains your project's presents classes.", cnfe);
}
}
/** A list of filesets that contain java source to be processed. */
protected List<FileSet> _filesets = Lists.newArrayList();
/** Show extra output if set. */
protected boolean _verbose;
/** Used to do our own classpath business. */
protected ClassLoader _cloader;
}
@@ -0,0 +1,278 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import com.samskivert.util.StringUtil;
/**
* Utility methods used by our various source code generating tasks.
*/
public class GenUtil extends com.samskivert.util.GenUtil
{
/** A regular expression for matching the package declaration. */
public static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s+(\\S+)\\W");
/** A regular expression for matching the class or interface declaration. */
public static final Pattern NAME_PATTERN =
Pattern.compile("^\\s*public\\s+(?:abstract\\s+)?(interface|class|enum)\\s+(\\S+)(\\W|$)");
/**
* Returns the name of the supplied class as it would appear in ActionScript code using the
* class (no package prefix, arrays specified as Array<code>Array</code>).
*/
public static String simpleASName (Class<?> clazz)
{
if (clazz.isArray()) {
Class<?> compoType = clazz.getComponentType();
if (Byte.TYPE.equals(compoType)) {
return "ByteArray";
} else if (Object.class.equals(compoType)) {
return "Array";
} else {
return "TypedArray /* of " + compoType + " */";
}
} 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 name.replace('$', '_');
}
}
/**
* "Boxes" the supplied argument, ie. turning an <code>int</code> into an <code>Integer</code>
* object.
*/
public static String boxArgument (Class<?> clazz, String name)
{
if (clazz == Boolean.TYPE) {
return "Boolean.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 "Long.valueOf(" + name + ")";
} 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 <code>Integer</code> object into an
* <code>int</code>.
*/
public static String unboxArgument (Type type, String name)
{
if (Boolean.TYPE.equals(type)) {
return "((Boolean)" + name + ").booleanValue()";
} else if (Byte.TYPE.equals(type)) {
return "((Byte)" + name + ").byteValue()";
} else if (Character.TYPE.equals(type)) {
return "((Character)" + name + ").charValue()";
} else if (Short.TYPE.equals(type)) {
return "((Short)" + name + ").shortValue()";
} else if (Integer.TYPE.equals(type)) {
return "((Integer)" + name + ").intValue()";
} else if (Long.TYPE.equals(type)) {
return "((Long)" + name + ").longValue()";
} else if (Float.TYPE.equals(type)) {
return "((Float)" + name + ").floatValue()";
} else if (Double.TYPE.equals(type)) {
return "((Double)" + name + ").doubleValue()";
} else if (Object.class.equals(type)) {
return name; // no need to cast object
} else {
return "(" + simpleName(type) + ")" + name;
}
}
/**
* "Boxes" the supplied argument, ie. turning an <code>int</code> into an <code>Integer</code>
* 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 <code>Integer</code> object into an
* <code>int</code>.
*/
public static String unboxASArgument (Class<?> clazz, String name)
{
if (clazz == Boolean.TYPE) {
return "(" + name + " as Boolean)";
} else if (clazz == Byte.TYPE ||
clazz == Character.TYPE ||
clazz == Short.TYPE ||
clazz == Integer.TYPE) {
return "(" + name + " as int)";
} else if (clazz == Long.TYPE) {
return "(" + name + " as Long)";
} else if (clazz == Float.TYPE ||
clazz == Double.TYPE) {
return "(" + name + " as Number)";
} else {
return "(" + name + " as " + simpleASName(clazz) + ")";
}
}
/**
* Potentially clones the supplied argument if it is the type that needs such treatment.
*/
public static String cloneArgument (Class<?> dsclazz, Field field, String name)
{
Class<?> clazz = field.getType();
if (clazz.isArray() || dsclazz.equals(clazz)) {
return "(" + name + " == null) ? null : " + name + ".clone()";
} else if (dsclazz.isAssignableFrom(clazz)) {
return "(" + name + " == null) ? null : " +
"(" + simpleName(field) + ")" + name + ".clone()";
} else {
return name;
}
}
/**
* Reads in the supplied source file and locates the package and class or interface name and
* returns a fully qualified class name.
*/
public static String readClassName (File source)
throws IOException
{
// load up the file and determine it's package and classname
String pkgname = null, name = null;
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(2);
break;
}
}
bin.close();
// make sure we found something
if (name == null) {
throw new IOException("Unable to locate class or interface name in " + source + ".");
}
// prepend the package name to get a name we can Class.forName()
if (pkgname != null) {
name = pkgname + "." + name;
}
return name;
}
/**
* Return an "@Generated" annotation.
*
* @param clazz the class doing the code generation, NOT the generation target.
* @param indent the number of spaces that the annotation is indented.
* @param includeDate include the date?
* @param comments joined by a space and used as explanatory comments.
*/
public static String getGeneratedAnnotation (
Class<?> clazz, int indent, boolean includeDate, String... comments)
{
final int LINE_LENGTH = 100;
String comm = StringUtil.join(comments, " ");
boolean hasComment = !StringUtil.isBlank(comm);
String anno = "@Generated(value={\"" + clazz.getName() + "\"}";
if (includeDate) {
anno += ",";
// ISO 8601 date
String date = " date=\"" +
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(new Date()) + "\"";
// wrap the date onto a new line if it's going to be too long, or if we're
// adding a comment (which is also on a new line)
if (hasComment || anno.length() + date.length() + 1 + indent > LINE_LENGTH) {
// put the date on a new line, space it right
date = "\n" + StringUtil.fill(' ', indent + 10) + date;
}
anno += date;
}
if (hasComment) {
anno += ",\n" + StringUtil.fill(' ', indent + 11) + "comments=\"" + comm + "\"";
}
anno += ")";
return anno;
}
}
@@ -0,0 +1,117 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.samskivert.util.Tuple;
/**
* Merges updates to a generated source file into a previously generated version of that source
* while leaving changes outside marked sections alone.
*/
public class GeneratedSourceMerger
{
/**
* Returns <code>previouslyGenerated</code> with marked sections updated from the same marked
* sections in <code>newlyGenerated</code> Everything outside these sections in
* <code>previouslyGenerated</code> is returned as is. A marked section starts with
* "// GENERATED <name> START" and ends with "// GENERATED <name> END"
*/
public String merge (String newlyGenerated, String previouslyGenerated)
throws Exception
{
// Extract the generated section names from the output and make sure they're all matched
Map<String, String> sections = Maps.newLinkedHashMap();
Matcher m = _sectionDelimiter.matcher(newlyGenerated);
while (m.find()) {
Tuple<String, String> section = extractGeneratedSection(m, newlyGenerated);
Preconditions.checkArgument(!sections.containsKey(section.left),
"Section '%s' used more than once", section.left);
sections.put(section.left, section.right);
}
// Merge with the previously generated source
StringBuilder merged = new StringBuilder();
m = _sectionDelimiter.matcher(previouslyGenerated);
Iterator<Entry<String, String>> secs = sections.entrySet().iterator();
int currentStart = 0;
while (m.find()) {
merged.append(previouslyGenerated.substring(currentStart, m.start()));
Tuple<String, String> existingSection =
extractGeneratedSection(m, previouslyGenerated);
// Allow generated sections to be dropped in the template, but warn in case something
// odd's happening
if (!secs.hasNext()) {
System.err.println("Dropping previously generated section '" + m.group(1)
+ "' that's no longer generated by the template");
}
// However, we don't allow sections to be removed or reordered in the generated code
Entry<String, String> newSection = secs.next();
Preconditions.checkArgument(newSection.getKey().equals(existingSection.left),
"Generated code expected '%s' section, but encountered '%s'", newSection.getKey(),
existingSection.left);
// Pop in the newly generated code in the place of what was there before
merged.append(newSection.getValue());
currentStart = m.end();
}
// Add generated sections that weren't present in the old output before the last
// non-generated code. It's a 50-50 shot, so warn when this happens
while(secs.hasNext()) {
Entry<String, String> newSection = secs.next();
System.err.println("Adding previously missing generated section '"
+ newSection.getKey() + "' before the last non-generated text");
merged.append(newSection.getValue());
}
// Add any text past the last previously generated section
merged.append(previouslyGenerated.substring(currentStart));
return merged.toString();
}
/**
* Returns a section name and its contents from the given matcher pointing to the start of a
* section. <code>m</code> is at the end of the section when this returns.
*/
protected Tuple<String, String> extractGeneratedSection (Matcher m, String input)
{
int startIdx = m.start();
String startName = m.group(1);
Preconditions.checkArgument(m.group(2).equals("START"), "'%s' END without START",
startName);
Preconditions.checkArgument(m.find(), "'%s' START without END", startName);
String endName = m.group(1);
Preconditions.checkArgument(m.group(2).equals("END"),
"'%s' START after '%s' START", endName, startName);
Preconditions.checkArgument(endName.equals(startName),
"'%s' END after '%s' START", endName, startName);
return Tuple.newTuple(startName, input.substring(startIdx, m.end()));
}
protected final Pattern _sectionDelimiter = Pattern.compile(" *// GENERATED (\\w+) (START|END)\n");
}
@@ -0,0 +1,374 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.StringUtil;
/**
* Manages a set of strings to be used as a set of imports. Provides useful functions for
* manipulating the set and sorts results.
*
* <p>Some methods in this class use a variable length String parameter 'replace'. This is a
* convenience for easily specifying multiple find/replace pairs. For example, to replace "Foo"
* with "Bar" and "123" with "ABC", a function can be called with the 4 arguments "Foo", "Bar",
* "123", "ABC" for the String... replace argument.
*
* <p>A few methods also use a "pattern" string parameter that is used to match a class name.
* This is a dumbed down regular expression (to avoid many \.) where "*" means .* and no other
* characters have special meaning. The pattern is also implicitly enclosed with ^$ so that the
* pattern must match the class name in its entirity. Callers will mostly use this to specify a
* prefix like "something*" or a suffix like "*something".
*/
public class ImportSet
{
/**
* Adds the given class' name to the set of imports.
* @param clazz the class to add
*/
public void add (Class<?> clazz)
{
_imports.add(clazz.getName());
}
/**
* Adds the given name to the set of imports.
* @param name the name to add
*/
public void add (String name)
{
if (name != null) {
_imports.add(name);
}
}
/**
* Adds all the imports from another import set into this one.
* @param other the import set whose imports should be added
*/
public void addAll (ImportSet other)
{
_imports.addAll(other._imports);
}
/**
* Adds a class' name to the imports but first performs the given list of search/replaces as
* described above.
* @param clazz the class whose name is munged and added
* @param replace array of pairs to search/replace on the name before adding
*/
public void addMunged (Class<?> clazz, String... replace)
{
String name = clazz.getName();
for (int ii = 0; ii < replace.length; ii += 2) {
name = name.replace(replace[ii], replace[ii+1]);
}
_imports.add(name);
}
@Override
public ImportSet clone ()
{
ImportSet newset = new ImportSet();
newset.addAll(this);
return newset;
}
/**
* Gets rid of primitive and java.lang imports.
*/
public void removeGlobals ()
{
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
if (name.indexOf('.') == -1) {
i.remove();
} else if (name.startsWith("java.lang")) {
i.remove();
}
}
}
/**
* Gets rid of array imports.
*/
public int removeArrays ()
{
return removeAll("[*");
}
/**
* Remove all classes that are in the same package.
* @param pkg package to remove
*/
public void removeSamePackage (String pkg)
{
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
if (name.startsWith(pkg) &&
name.indexOf('.', pkg.length() + 1) == -1) {
i.remove();
}
}
}
/**
* Replaces inner class imports (those with a '$') with an import of the parent class.
*/
public void swapInnerClassesForParents ()
{
ImportSet declarers = new ImportSet();
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
int dollar = name.indexOf('$');
if (dollar >= 0) {
i.remove();
declarers.add(name.substring(0, dollar));
}
}
addAll(declarers);
}
/**
* Replace all inner classes' separator characters ('$') with an underscore ('_') for use
* when generating ActionScript.
*/
public void translateInnerClasses ()
{
ImportSet inner = new ImportSet();
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
int dollar = name.indexOf('$');
if (dollar >= 0) {
i.remove();
inner.add(name.replace('$', '_'));
}
}
addAll(inner);
}
/**
* Inserts imports for the non-primitive classes contained in all array imports.
*/
public void translateClassArrays ()
{
ImportSet arrayTypes = new ImportSet();
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
int bracket = name.lastIndexOf('[');
if (bracket != -1 &&
name.charAt(bracket + 1) == 'L') {
arrayTypes.add(name.substring(bracket + 2, name.length() - 1));
}
}
addAll(arrayTypes);
}
/**
* Temporarily remove one import matching the given pattern. The most recently pushed pattern
* can be re-added using <code>popIn</code>. If there is no match, a null value is pushed so
* that popIn can still be called.
* @param pattern to match
*/
public void pushOut (String pattern)
{
Pattern pat = makePattern(pattern);
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String imp = i.next();
if (pat.matcher(imp).matches()) {
i.remove();
_pushed.add(imp);
return;
}
}
_pushed.add(null);
}
/**
* Re-adds the most recently popped import to the set. If a null value was pushed, does
* nothing.
* @throws IndexOutOfBoundsException if there is nothing to pop
*/
public void popIn ()
{
String front = _pushed.remove(_pushed.size() - 1);
if (front != null) {
_imports.add(front);
}
}
/**
* Removes the name of a class from the imports.
* @param clazz the class whose name should be removed
*/
public void remove (Class<?> clazz)
{
_imports.remove(clazz.getName());
}
/**
* Replaces any import exactly each find string with the corresponding replace string.
* See the description above.
* @param replace array of pairs for search/replace
*/
public void replace (String... replace)
{
HashSet<String> toAdd = Sets.newHashSet();
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
for (int j = 0; j < replace.length; j += 2) {
if (name.equals(replace[j])) {
toAdd.add(replace[j + 1]);
i.remove();
break;
}
}
}
_imports.addAll(toAdd);
}
/**
* Replace the import matchings a class name.
* @param pattern the class whose name to match
* @param replacement string to use instead
*/
public void replace (Class<?> pattern, String replacement)
{
replace(pattern.getName(), replacement);
}
/**
* Remove all imports matching the given pattern.
* @param pattern the dumbed down regex to match (see description above)
* @return the number of imports removed
*/
public int removeAll (String pattern)
{
Pattern pat = makePattern(pattern);
int removed = 0;
Iterator<String> i = _imports.iterator();
while (i.hasNext()) {
String name = i.next();
if (pat.matcher(name).matches()) {
i.remove();
++removed;
}
}
return removed;
}
/**
* Adds a new munged import for each existing import that matches a pattern. The new entry is
* a copy of the old entry but modified according to the given find/replace pairs (see
* description above).
* @param pattern to qualify imports to duplicate
* @param replace pairs to find/replace on the new import
*/
public void duplicateAndMunge (String pattern, String... replace)
{
Pattern pat = makePattern(pattern);
HashSet<String> toMunge = Sets.newHashSet();
for (String name : _imports) {
if (pat.matcher(name).matches()) {
toMunge.add(name);
}
}
for (String name : toMunge) {
String newname = name;
for (int ii = 0; ii < replace.length; ii += 2) {
newname = newname.replace(replace[ii], replace[ii + 1]);
}
_imports.add(newname);
}
}
/**
* Convert the set of imports to a sorted list, ready to be output to a generated file.
* @return the sorted list of imports
*/
public List<String> toList ()
{
ComparableArrayList<String> list = new ComparableArrayList<String>();
list.addAll(_imports);
list.sort();
return list;
}
@Override
public String toString ()
{
return StringUtil.toString(_imports);
}
/**
* Create a real regular expression from the dumbed down input.
* @param input the dumbed down wildcard expression
* @return the calculated regular expression
*/
protected static Pattern makePattern (String input)
{
StringBuilder pattern = new StringBuilder();
pattern.append("^");
while (true) {
String[] parts = _splitter.split(input, 2);
pattern.append(Pattern.quote(parts[0]));
if (parts.length == 1) {
break;
}
int length = parts[0].length();
String wildcard = input.substring(length, length + 1);
if (wildcard.equals("*")) {
pattern.append(".*");
} else {
System.err.println("Bad wildcard " + wildcard);
}
input = parts[1];
}
pattern.append("$");
return Pattern.compile(pattern.toString());
}
protected HashSet<String> _imports = Sets.newHashSet();
protected List<String> _pushed = Lists.newArrayList();
protected static Pattern _splitter = Pattern.compile("\\*");
}
@@ -0,0 +1,358 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.ArrayList;
import java.util.HashSet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
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.Path;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.samskivert.io.StreamUtil;
import com.threerings.io.BasicStreamers;
import com.threerings.io.FieldMarshaller;
import com.threerings.io.Streamable;
/**
* Instruments compiled {@link Streamable} classes with public methods that can be used to stream
* protected and private members when running in a sandboxed JVM.
*/
public class InstrumentStreamableTask extends Task
{
/**
* Adds a nested &lt;fileset&gt; element which enumerates streamable class files.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Adds a &lt;path&gt; element which defines our classpath.
*/
public void addPath (Path path)
{
_paths.add(path);
}
/**
* Configures the directory into which we write our instrumented class files.
*/
public void setOutdir (File outdir)
{
_outdir = outdir;
}
@Override
public void execute ()
{
// configure our ClassPool with our classpath
for (Path path : _paths) {
for (String element : path.list()) {
try {
_pool.appendClassPath(element);
} catch (NotFoundException nfe) {
System.err.println("Invalid classpath entry [path=" + element + "]: " + nfe);
}
}
}
// instantiate streamable
try {
_streamable = _pool.get(Streamable.class.getName());
} catch (Exception e) {
throw new BuildException("Unable to load " + Streamable.class.getName() + ": " + e);
}
// now process the files
for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (String srcFile : srcFiles) {
processClass(new File(fromDir, srcFile));
}
}
}
/**
* Processes a class file.
*/
protected void processClass (File source)
{
CtClass clazz;
InputStream in = null;
try {
clazz = _pool.makeClass(in = new BufferedInputStream(new FileInputStream(source)));
} catch (IOException ioe) {
System.err.println("Failed to load " + source + ": " + ioe);
return;
} finally {
StreamUtil.close(in);
}
try {
if (clazz.subtypeOf(_streamable)) {
processStreamable(source, clazz);
}
} catch (NotFoundException nfe) {
System.err.println("Error processing class [class=" + clazz.getName() +
", error=" + nfe + "].");
}
}
/**
* Instruments the supplied {@link Streamable} implementing class.
*/
protected void processStreamable (File source, CtClass clazz)
throws NotFoundException
{
ArrayList<CtField> fields = Lists.newArrayList();
for (CtField field : clazz.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers) ||
!(Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers))) {
continue;
}
fields.add(field);
}
HashSet<String> methods = Sets.newHashSet();
for (CtMethod method : clazz.getMethods()) {
methods.add(method.getName());
}
int added = 0;
for (CtField field : fields) {
String rname = FieldMarshaller.getReaderMethodName(field.getName());
if (!methods.contains(rname)) {
String reader =
"public void " + rname + " (com.threerings.io.ObjectInputStream ins) {\n" +
// " throws java.io.IOException, java.lang.ClassNotFoundException\n" +
" " + getFieldReader(field) + "\n" +
"}";
// System.out.println("Adding reader " + clazz.getName() + ":\n" + reader);
try {
clazz.addMethod(CtNewMethod.make(reader, clazz));
added++;
} catch (CannotCompileException cce) {
System.err.println("Unable to compile reader [class=" + clazz.getName() +
", error=" + cce + "]:");
System.err.println(reader);
}
}
String wname = FieldMarshaller.getWriterMethodName(field.getName());
if (!methods.contains(wname)) {
String writer =
"public void " + wname + " (com.threerings.io.ObjectOutputStream out) {\n" +
// " throws java.io.IOException\n" +
" " + getFieldWriter(field) + "\n" +
"}";
// System.out.println("Adding writer " + clazz.getName() + ":\n" + writer);
try {
clazz.addMethod(CtNewMethod.make(writer, clazz));
added++;
} catch (CannotCompileException cce) {
System.err.println("Unable to compile writer [class=" + clazz.getName() +
", error=" + cce + "]:");
System.err.println(writer);
}
}
}
if (added > 0) {
try {
System.out.println("Instrumented '" + clazz.getName() + "'.");
clazz.writeFile(_outdir.getPath());
} catch (Exception e) {
System.err.println("Failed to write instrumented class [class=" + clazz +
", outdir=" + _outdir + "]: " + e);
}
}
}
protected String getFieldReader (CtField field)
throws NotFoundException
{
CtClass type = field.getType();
String name = field.getName();
if (type.getName().equals("java.lang.String")) {
return readWrap(field, name + " = ins.readUTF();");
} else if (type.equals(CtClass.booleanType) || type.getName().equals("java.lang.Boolean")) {
return readWrap(field, name + " = ins.readBoolean();");
} else if (type.equals(CtClass.byteType) || type.getName().equals("java.lang.Byte")) {
return readWrap(field, name + " = ins.readByte();");
} else if (type.equals(CtClass.shortType) || type.getName().equals("java.lang.Short")) {
return readWrap(field, name + " = ins.readShort();");
} else if (type.equals(CtClass.intType) || type.getName().equals("java.lang.Integer")) {
return readWrap(field, name + " = ins.readInt();");
} else if (type.equals(CtClass.longType) || type.getName().equals("java.lang.Long")) {
return readWrap(field, name + " = ins.readLong();");
} else if (type.equals(CtClass.floatType) || type.getName().equals("java.lang.Float")) {
return readWrap(field, name + " = ins.readFloat();");
} else if (type.equals(CtClass.doubleType) || type.getName().equals("java.lang.Double")) {
return readWrap(field, name + " = ins.readDouble();");
}
if (type.isArray()) {
CtClass ctype = type.getComponentType();
if (ctype.equals(CtClass.booleanType)) {
return readWrap(field, name + " = " + BSNAME + ".readBooleanArray(ins);");
} else if (ctype.equals(CtClass.byteType)) {
return readWrap(field, name + " = " + BSNAME + ".readByteArray(ins);");
} else if (ctype.equals(CtClass.shortType)) {
return readWrap(field, name + " = " + BSNAME + ".readShortArray(ins);");
} else if (ctype.equals(CtClass.intType)) {
return readWrap(field, name + " = " + BSNAME + ".readIntArray(ins);");
} else if (ctype.equals(CtClass.longType)) {
return readWrap(field, name + " = " + BSNAME + ".readLongArray(ins);");
} else if (ctype.equals(CtClass.floatType)) {
return readWrap(field, name + " = " + BSNAME + ".readFloat(ins);");
} else if (ctype.equals(CtClass.doubleType)) {
return readWrap(field, name + " = " + BSNAME + ".readDoubleArray(ins);");
} else if (ctype.getName().equals("java.lang.Object")) {
return readWrap(field, name + " = " + BSNAME + ".readObjectArray(ins);");
}
}
// no need to wrap streamable instances
return (name + " = (" + type.getName() + ")ins.readObject();");
}
protected String getFieldWriter (CtField field)
throws NotFoundException
{
CtClass type = field.getType();
String name = field.getName();
if (type.equals(CtClass.booleanType) || type.getName().equals("java.lang.Boolean")) {
return writeWrap(field, "out.writeBoolean(" + name + ");");
} else if (type.equals(CtClass.byteType) || type.getName().equals("java.lang.Byte")) {
return writeWrap(field, "out.writeByte(" + name + ");");
} else if (type.equals(CtClass.shortType) || type.getName().equals("java.lang.Short")) {
return writeWrap(field, "out.writeShort(" + name + ");");
} else if (type.equals(CtClass.intType) || type.getName().equals("java.lang.Integer")) {
return writeWrap(field, "out.writeInt(" + name + ");");
} else if (type.equals(CtClass.longType) || type.getName().equals("java.lang.Long")) {
return writeWrap(field, "out.writeLong(" + name + ");");
} else if (type.equals(CtClass.floatType) || type.getName().equals("java.lang.Float")) {
return writeWrap(field, "out.writeFloat(" + name + ");");
} else if (type.equals(CtClass.doubleType) || type.getName().equals("java.lang.Double")) {
return writeWrap(field, "out.writeDouble(" + name + ");");
} else if (type.getName().equals("java.lang.String")) {
return writeWrap(field, "out.writeUTF(" + name + ");");
}
if (type.isArray()) {
CtClass ctype = type.getComponentType();
if (ctype.equals(CtClass.booleanType)) {
return writeWrap(field, BSNAME + ".writeBooleanArray(out, " + name + ");");
} else if (ctype.equals(CtClass.byteType)) {
return writeWrap(field, BSNAME + ".writeByteArray(out, " + name + ");");
} else if (ctype.equals(CtClass.shortType)) {
return writeWrap(field, BSNAME + ".writeShortArray(out, " + name + ");");
} else if (ctype.equals(CtClass.intType)) {
return writeWrap(field, BSNAME + ".writeIntArray(out, " + name + ");");
} else if (ctype.equals(CtClass.longType)) {
return writeWrap(field, BSNAME + ".writeLongArray(out, " + name + ");");
} else if (ctype.equals(CtClass.floatType)) {
return writeWrap(field, BSNAME + ".writeFloat(out, " + name + ");");
} else if (ctype.equals(CtClass.doubleType)) {
return writeWrap(field, BSNAME + ".writeDoubleArray(out, " + name + ");");
} else if (ctype.getName().equals("java.lang.Object")) {
return writeWrap(field, BSNAME + ".writeObjectArray(out, " + name + ");");
}
}
// no need to wrap streamable instances
return "out.writeObject(" + name + ");";
}
protected String readWrap (CtField field, String body)
throws NotFoundException
{
if (field.getType().isPrimitive()) {
return body;
} else {
return "if (ins.readBoolean()) {\n" +
" " + body + "\n" +
" } else {\n" +
" " + field.getName() + " = null;\n" +
" }";
}
}
protected String writeWrap (CtField field, String body)
throws NotFoundException
{
if (field.getType().isPrimitive()) {
return body;
} else {
return "if (" + field.getName() + " == null) {\n" +
" out.writeBoolean(false);\n" +
" } else {\n" +
" out.writeBoolean(true);\n" +
" " + body + "\n" +
" }";
}
}
/** A list of filesets that contain Streamable class files. */
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
/** A list of paths that make up our classpath. */
protected ArrayList<Path> _paths = Lists.newArrayList();
/** The directory to which we write our instrumented class files. */
protected File _outdir;
/** Used to instrument class files. */
protected ClassPool _pool = ClassPool.getDefault();
/** Used to determine which classes implement {@link Streamable}. */
protected CtClass _streamable;
protected static final String BSNAME = BasicStreamers.class.getName();
}
@@ -0,0 +1,449 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.List;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.tools.ant.BuildException;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.samskivert.io.StreamUtil;
import com.samskivert.util.Logger;
import com.samskivert.util.StringUtil;
import com.threerings.presents.annotation.TransportHint;
import com.threerings.presents.net.Transport;
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 GenTask
{
/** Used to keep track of invocation service method listener arguments. */
public class ListenerArgument
{
public Class<?> listener;
public ListenerArgument (int index, Class<?> listener) {
this.listener = listener;
_index = index;
}
public String getMarshaller () {
String name = GenUtil.simpleName(listener);
// handle ye olde special case
if (name.equals("InvocationService.InvocationListener")) {
return "ListenerMarshaller";
}
name = name.replace("Service", "Marshaller");
return name.replace("Listener", "Marshaller");
}
public String getActionScriptMarshaller () {
// handle ye olde special case
String name = listener.getName();
if (name.endsWith("InvocationService$InvocationListener")) {
return "InvocationMarshaller_ListenerMarshaller";
} else {
return getMarshaller().replace('.', '_');
}
}
public int getIndex () {
return _index+1;
}
public int getIndexSkipFirst () {
return _index;
}
protected int _index;
}
/** Used to keep track of invocation service methods or listener methods. */
public class ServiceMethod implements Comparable<ServiceMethod>
{
public Method method;
public List<ListenerArgument> listenerArgs = Lists.newArrayList();
/**
* Creates a new service method.
* @param method the method to inspect
* @param imports will be filled with the types required by the method
*/
public ServiceMethod (Method method, ImportSet imports) {
this.method = method;
// if this method has listener arguments, we need to add listener argument info for them
Class<?>[] args = method.getParameterTypes();
for (int ii = 0; ii < args.length; ii++) {
Class<?> arg = args[ii];
while (arg.isArray()) {
arg = arg.getComponentType();
}
if (_ilistener.isAssignableFrom(arg)) {
listenerArgs.add(new ListenerArgument(ii, arg));
}
}
// we need to look through our arguments and note any needed imports in the supplied
// table
for (Type type : method.getGenericParameterTypes()) {
addImportsForType(type, imports);
}
// import Transport if used
if (!StringUtil.isBlank(getTransport())) {
imports.add(Transport.class);
}
}
public String getCode () {
return StringUtil.unStudlyName(method.getName()).toUpperCase();
}
public String getSenderMethodName () {
String mname = method.getName();
if (mname.startsWith("received")) {
return "send" + mname.substring("received".length());
} else {
return mname;
}
}
public String getArgListSkipFirst () {
return getArgList(true);
}
public String getArgList () {
return getArgList(false);
}
public String getArgList (boolean skipFirst) {
StringBuilder buf = new StringBuilder();
Type[] ptypes = method.getGenericParameterTypes();
for (int ii = skipFirst ? 1 : 0; ii < ptypes.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
String simpleName = GenUtil.simpleName(ptypes[ii]);
if (method.isVarArgs() && ii == ptypes.length - 1) {
// Switch [] with ... for varargs
buf.append(simpleName.substring(0, simpleName.length() - 2)).append("...");
} else {
buf.append(simpleName);
}
buf.append(" arg").append(skipFirst ? ii : ii+1);
}
return buf.toString();
}
public String getASArgListSkipFirst () {
return getASArgList(true);
}
public String getASArgList () {
return getASArgList(false);
}
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 getASInvokArgList () {
StringBuilder buf = new StringBuilder();
Class<?>[] args = method.getParameterTypes();
for (int ii = 0; ii < args.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append("arg").append(ii);
}
return buf.toString();
}
public String getWrappedArgListSkipFirst () {
return getWrappedArgList(true);
}
public String getWrappedArgList () {
return getWrappedArgList(false);
}
public String getWrappedArgList (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(boxArgument(args[ii], ii+1));
}
return buf.toString();
}
public String getASWrappedArgListSkipFirst () {
return getASWrappedArgList(true);
}
public String getASWrappedArgList () {
return getASWrappedArgList(false);
}
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 index = String.valueOf(skipFirst ? ii : (ii+1));
String arg;
if (_ilistener.isAssignableFrom(args[ii])) {
arg = GenUtil.boxASArgument(args[ii], "listener" + index);
} else {
arg = GenUtil.boxASArgument(args[ii], "arg" + index);
}
buf.append(arg);
}
return buf.toString();
}
public boolean hasArgsSkipFirst () {
return (method.getParameterTypes().length > 1);
}
public boolean hasArgs () {
return (method.getParameterTypes().length > 0);
}
public boolean hasParameterizedArgs () {
return Iterables.any(
Arrays.asList(method.getGenericParameterTypes()), new Predicate<Type>() {
public boolean apply (Type type) {
// TODO: might eventually need to handle generic arrays and wildcard types
return (type instanceof ParameterizedType);
}
});
}
public String getUnwrappedArgListAsListeners () {
return getUnwrappedArgList(true);
}
public String getUnwrappedArgList () {
return getUnwrappedArgList(false);
}
public String getUnwrappedArgList (boolean listenerMode) {
StringBuilder buf = new StringBuilder();
Type[] ptypes = method.getGenericParameterTypes();
for (int ii = (listenerMode ? 0 : 1); ii < ptypes.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append(unboxArgument(ptypes[ii], listenerMode ? ii : ii-1, listenerMode));
}
return buf.toString();
}
public String getASUnwrappedArgListAsListeners () {
return getASUnwrappedArgList(true);
}
public String getASUnwrappedArgList () {
return getASUnwrappedArgList(false);
}
public String getASUnwrappedArgList (boolean listenerMode) {
StringBuilder buf = new StringBuilder();
Class<?>[] args = method.getParameterTypes();
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();
}
public String getTransport () {
TransportHint hint = method.getAnnotation(TransportHint.class);
if (hint == null) {
// inherit hint from interface annotation
hint = method.getDeclaringClass().getAnnotation(TransportHint.class);
}
if (hint == null) {
return "";
}
return ", Transport.getInstance(Transport.Type." +
hint.type().name() + ", " + hint.channel() + ")";
}
// from interface Comparator<ServiceMethod>
public int compareTo (ServiceMethod other) {
return getCode().compareTo(other.getCode());
}
@Override // from Object
public boolean equals (Object other) {
return (other instanceof ServiceMethod) && compareTo((ServiceMethod)other) == 0;
}
@Override // from Object
public int hashCode () {
return getCode().hashCode();
}
protected void addImportsForType (Type type, ImportSet imports) {
if (type instanceof Class<?>) {
imports.add((Class<?>)type);
} else if (type instanceof ParameterizedType) {
imports.add((Class<?>)((ParameterizedType)type).getRawType());
for (Type param : ((ParameterizedType)type).getActualTypeArguments()) {
addImportsForType(param, imports);
}
} else if (type instanceof WildcardType) {
for (Type upper : ((WildcardType)type).getUpperBounds()) {
addImportsForType(upper, imports);
}
for (Type lower : ((WildcardType)type).getLowerBounds()) {
addImportsForType(lower, imports);
}
} else if (type instanceof GenericArrayType) {
addImportsForType(((GenericArrayType)type).getGenericComponentType(), imports);
} else {
System.err.println(Logger.format(
"Unhandled Type in adding imports for a service", "type", type, "typeClass",
type.getClass()));
}
}
protected String boxArgument (Class<?> clazz, int index) {
if (_ilistener.isAssignableFrom(clazz)) {
return GenUtil.boxArgument(clazz, "listener" + index);
} else {
return GenUtil.boxArgument(clazz, "arg" + index);
}
}
protected String unboxArgument (Type type, int index, boolean listenerMode) {
if (listenerMode && (type instanceof Class<?>) &&
_ilistener.isAssignableFrom((Class<?>)type)) {
return "listener" + index;
} else {
return GenUtil.unboxArgument(type, "args[" + index + "]");
}
}
}
/**
* Configures us with a header file that we'll prepend to all
* generated source files.
*/
public void setHeader (File header)
{
try {
_header = StreamUtil.toString(new FileReader(header));
} catch (IOException ioe) {
System.err.println("Unabled to load header '" + header + ": " +
ioe.getMessage());
}
}
@Override
public void execute ()
{
// resolve the InvocationListener class using our classloader
_ilistener = loadClass(InvocationListener.class.getName());
super.execute();
}
protected void writeFile (String path, String data)
throws IOException
{
if (_verbose) {
System.out.println("Writing file " + path);
}
if (_header != null) {
data = _header + data;
}
File dest = new File(path);
if (!dest.getParentFile().exists() && !dest.getParentFile().mkdirs()) {
throw new BuildException("Unable to create directory for " + dest.getAbsolutePath());
}
new PrintWriter(dest, "UTF-8").append(data).close();
}
protected static <T> void checkedAdd (List<T> list, T value)
{
if (!list.contains(value)) {
list.add(value);
}
}
protected static String replacePath (String source, String oldstr, String newstr)
{
return source.replace(oldstr.replace('/', File.separatorChar),
newstr.replace('/', File.separatorChar));
}
/** A header to put on all generated source files. */
protected String _header;
/** {@link InvocationListener} resolved with the proper classloader so
* that we can compare it to loaded derived classes. */
protected Class<?> _ilistener;
}
@@ -0,0 +1,274 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.List;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.google.common.collect.Lists;
import com.samskivert.util.StringUtil;
/**
* Reads in a source file, allows the replacement of a "generated fields" and "generated methods"
* section and writing of the file back out to the disk.
*/
public class SourceFile
{
/**
* Reads the code from the supplied source file.
*/
public void readFrom (File source)
throws IOException
{
// slurp our source file into newline separated strings
_lines = Lists.newArrayList();
BufferedReader bin = new BufferedReader(new FileReader(source));
String line = null;
while ((line = bin.readLine()) != null) {
_lines.add(line);
}
bin.close();
// now determine where to insert our static field declarations and our generated methods
int bstart = -1, bend = -1;
for (int ii = 0, nn = _lines.size(); ii < nn; ii++) {
line = _lines.get(ii).trim();
// look for the start of the class body
if (GenUtil.NAME_PATTERN.matcher(line).find()) {
if (line.endsWith("{")) {
bstart = ii+1;
} else {
// search down a few lines for the open brace
for (int oo = 1; oo < 10; oo++) {
if (safeGetLine(ii+oo).trim().endsWith("{")) {
bstart = ii+oo+1;
break;
}
}
}
// track the last } on a line by itself and we'll call that the end of the class body
} else if (line.equals("}")) {
bend = ii;
// look for our field and method markers
} else if (line.equals(FIELDS_START)) {
_nstart = ii;
} else if (line.equals(FIELDS_END)) {
_nend = ii+1;
} else if (line.equals(METHODS_START)) {
_mstart = ii;
} else if (line.equals(METHODS_END)) {
_mend = ii+1;
}
}
// sanity check the markers
check(source, "fields start", _nstart, "fields end", _nend);
check(source, "fields end", _nend, "fields start", _nstart);
check(source, "methods start", _mstart, "methods end", _mend);
check(source, "methods end", _mend, "methods start", _mstart);
// we have no previous markers then stuff the fields at the top of the class body and the
// methods at the bottom
if (_nstart == -1) {
_nstart = bstart;
_nend = bstart;
}
if (_mstart == -1) {
_mstart = bend;
_mend = bend;
}
}
/**
* Returns true if the supplied text appears in the non-auto-generated section.
*/
public boolean containsString (String text)
{
for (int ii = 0, nn = _lines.size(); ii < nn; ii++) {
// don't look inside the autogenerated areas
if (!(ii >= _nstart && ii < _nend) && !(ii >= _mstart && ii < _mend) &&
_lines.get(ii).contains(text)) {
return true;
}
}
return false;
}
/**
* Writes the code out to the specified file.
*
* @param fsection the new "generated fields" to write to the file, or null.
* @param msection the new "generated methods" to write to the file, or null.
*/
public void writeTo (File dest, String fsection, String msection)
throws IOException
{
BufferedWriter bout = new BufferedWriter(new FileWriter(dest));
addOrRemoveGeneratedImport(StringUtil.deNull(fsection).contains("@Generated(") ||
StringUtil.deNull(msection).contains("@Generated("));
// write the preamble
for (int ii = 0; ii < _nstart; ii++) {
writeln(bout, _lines.get(ii));
}
// write the field section
if (!StringUtil.isBlank(fsection)) {
String prev = safeGetLine(_nstart-1);
if (!StringUtil.isBlank(prev) && !prev.equals("{")) {
bout.newLine();
}
writeln(bout, " " + FIELDS_START);
bout.write(fsection);
writeln(bout, " " + FIELDS_END);
if (!StringUtil.isBlank(safeGetLine(_nend))) {
bout.newLine();
}
}
// write the mid-amble
for (int ii = _nend; ii < _mstart; ii++) {
writeln(bout, _lines.get(ii));
}
// write the method section
if (!StringUtil.isBlank(msection)) {
if (!StringUtil.isBlank(safeGetLine(_mstart-1))) {
bout.newLine();
}
writeln(bout, " " + METHODS_START);
bout.write(msection);
writeln(bout, " " + METHODS_END);
String next = safeGetLine(_mend);
if (!StringUtil.isBlank(next) && !next.equals("}")) {
bout.newLine();
}
}
// write the postamble
for (int ii = _mend, nn = _lines.size(); ii < nn; ii++) {
writeln(bout, _lines.get(ii));
}
bout.close();
}
/** Helper function for sanity checking marker existence. */
protected void check (File source, String mname, int mline, String fname, int fline)
throws IOException
{
if (mline == -1 && fline != -1) {
throw new IOException(
"Found " + fname + " marker (at line " + (fline+1) + ") but no " + mname +
" marker in '" + source + "'.");
}
}
/** Safely gets the <code>index</code>th line, returning the empty string if we exceed the
* length of the array. */
protected String safeGetLine (int index)
{
return (index < _lines.size()) ? _lines.get(index) : "";
}
/** Helper function for writing a string and a newline to a writer. */
protected void writeln (BufferedWriter bout, String line)
throws IOException
{
bout.write(line);
bout.newLine();
}
/**
* Add or remove an import for "@Generated", if needed.
*/
protected void addOrRemoveGeneratedImport (boolean add)
{
final String IMPORT = "import javax.annotation.Generated;";
int packageLine = -1;
int importLine = -1;
int lastJavaImport = -1;
int firstNonJavaImport = -1;
for (int ii = 0, nn = _lines.size(); ii < nn; ii++) {
String line = _lines.get(ii);
if (line.startsWith(IMPORT)) {
if (add) {
return; // we already got one!
}
importLine = ii;
break;
} else if (line.startsWith("package ")) {
packageLine = ii;
} else if (line.startsWith("import java")) {
lastJavaImport = ii;
} else if (firstNonJavaImport == -1 && line.startsWith("import ")) {
firstNonJavaImport = ii;
}
}
if (importLine != -1) {
// we must be removing, or we'd have already exited
_lines.remove(importLine);
} else if (!add) {
return; // it's already not there!
} else {
importLine = (lastJavaImport != -1) ? lastJavaImport + 1
: ((firstNonJavaImport != -1) ? firstNonJavaImport : packageLine + 1);
_lines.add(importLine, IMPORT);
}
// the import line is always above these other lines, so they can be adjusted wholesale
int adjustment = add ? 1 : -1;
_nstart += adjustment;
_nend += adjustment;
_mstart += adjustment;
_mend += adjustment;
}
protected List<String> _lines;
protected int _nstart = -1, _nend = -1;
protected int _mstart = -1, _mend = -1;
// markers
protected static final String MARKER = "// AUTO-GENERATED: ";
protected static final String FIELDS_START = MARKER + "FIELDS START";
protected static final String FIELDS_END = MARKER + "FIELDS END";
protected static final String METHODS_START = MARKER + "METHODS START";
protected static final String METHODS_END = MARKER + "METHODS END";
}
@@ -0,0 +1,54 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import com.google.common.collect.Lists;
import com.threerings.io.Streamable;
public class StreamableClassRequirements
{
public final List<Field> streamedFields = Lists.newArrayList();
public final boolean superclassStreamable;
public final Class<?> klass;
public StreamableClassRequirements(Class<?> klass)
{
this.klass = klass;
superclassStreamable =
klass.getSuperclass() != null && Streamable.class.isAssignableFrom(klass.getSuperclass());
for (Field field : klass.getDeclaredFields()) {
int mods = field.getModifiers();
if (Modifier.isStatic(mods) || Modifier.isTransient(mods)) {
continue;
}
streamedFields.add(field);
}
}
}
@@ -0,0 +1,53 @@
package {{package}};
{{#imports}}
import {{this}};
{{/imports}}
/**
* Dispatches calls to a {@link {{name}}Receiver} instance.
*/
public class {{name}}Decoder extends InvocationDecoder
{
/** The generated hash code used to identify this receiver class. */
public static final String RECEIVER_CODE = "{{receiver_code}}";
{{#methods}}
/** The method id used to dispatch {@link {{name}}Receiver#{{method.name}}}
* notifications. */
public static final int {{code}} = {{-index}};
{{/methods}}
/**
* Creates a decoder that may be registered to dispatch invocation
* service notifications to the specified receiver.
*/
public {{name}}Decoder ({{name}}Receiver receiver)
{
this.receiver = receiver;
}
@Override
public String getReceiverCode ()
{
return RECEIVER_CODE;
}
@Override
public void dispatchNotification (int methodId, Object[] args)
{
switch (methodId) {
{{#methods}}
case {{code}}:
(({{name}}Receiver)receiver).{{method.name}}(
{{getUnwrappedArgListAsListeners}}
);
return;
{{/methods}}
default:
super.dispatchNotification(methodId, args);
return;
}
}
}
@@ -0,0 +1,49 @@
package {{package}};
import javax.annotation.Generated;
{{#imports}}
import {{this}};
{{/imports}}
/**
* Dispatches requests to the {@link {{name}}Provider}.
*/
{{generated}}
public class {{name}}Dispatcher extends InvocationDispatcher<{{name}}Marshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public {{name}}Dispatcher ({{name}}Provider provider)
{
this.provider = provider;
}
@Override
public {{name}}Marshaller createMarshaller ()
{
return new {{name}}Marshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
{{#methods}}
case {{name}}Marshaller.{{code}}:
(({{name}}Provider)provider).{{method.name}}(
source{{#hasArgsSkipFirst}}, {{/hasArgsSkipFirst}}{{getUnwrappedArgList}}
);
return;
{{/methods}}
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,34 @@
/**
* Requests that the <code>{{field}}</code> field be set to the
* specified value. The local value will be updated immediately and an
* event will be propagated through the system to notify all listeners
* that the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
{{generated}}
public void set{{upfield}} ({{type}} value)
{
{{type}} ovalue = this.{{field}};
requestAttributeChange(
{{capfield}}, {{wrapfield}}, {{wrapofield}}{{transport}});
this.{{field}} = {{clonefield}};
}{{#have_elem}}
/**
* Requests that the <code>index</code>th element of
* <code>{{field}}</code> field be set to the specified value.
* The local value will be updated immediately and an event will be
* propagated through the system to notify all listeners that the
* attribute did change. Proxied copies of this object (on clients)
* will apply the value change when they received the attribute
* changed notification.
*/
{{generated}}
public void set{{upfield}}At ({{elemtype}} value, int index)
{
{{elemtype}} ovalue = this.{{field}}[index];
requestElementUpdate(
{{capfield}}, index, {{wrapelem}}, {{wrapoelem}}{{transport}});
this.{{field}}[index] = value;
}{{/have_elem}}
@@ -0,0 +1,3 @@
/** The field name of the <code>{{field}}</code> field. */
{{generated}}
public static final String {{capfield}} = "{{field}}";
@@ -0,0 +1,21 @@
/**
* Requests that <code>oid</code> be added to the <code>{{field}}</code>
* oid list. The list will not change until the event is actually
* propagated through the system.
*/
{{generated}}
public void addTo{{upfield}} (int oid)
{
requestOidAdd({{capfield}}, oid);
}
/**
* Requests that <code>oid</code> be removed from the
* <code>{{field}}</code> oid list. The list will not change until the
* event is actually propagated through the system.
*/
{{generated}}
public void removeFrom{{upfield}} (int oid)
{
requestOidRemove({{capfield}}, oid);
}
@@ -0,0 +1,50 @@
/**
* Requests that the specified entry be added to the
* <code>{{field}}</code> set. The set will not change until the event is
* actually propagated through the system.
*/
{{generated}}
public void addTo{{upfield}} ({{etype}} elem)
{
requestEntryAdd({{capfield}}, {{field}}, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>{{field}}</code> set. The set will not change until the
* event is actually propagated through the system.
*/
{{generated}}
public void removeFrom{{upfield}} (Comparable<?> key)
{
requestEntryRemove({{capfield}}, {{field}}, key);
}
/**
* Requests that the specified entry be updated in the
* <code>{{field}}</code> set. The set will not change until the event is
* actually propagated through the system.
*/
{{generated}}
public void update{{upfield}} ({{etype}} elem)
{
requestEntryUpdate({{capfield}}, {{field}}, elem{{transport}});
}
/**
* Requests that the <code>{{field}}</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
{{generated}}
public void set{{upfield}} ({{type}} value)
{
requestAttributeChange({{capfield}}, value, this.{{field}});
{{type}} clone = {{clonefield}};
this.{{field}} = clone;
}
@@ -0,0 +1,85 @@
package {{package}};
import javax.annotation.Generated;
{{#imports}}
import {{this}};
{{/imports}}
/**
* 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.
*/
{{generated}}
public class {{name}}Marshaller extends InvocationMarshaller
implements {{name}}Service
{
{{#listeners}}
/**
* Marshalls results to implementations of {@link {{name}}Service.{{listenerName}}Listener}.
*/
public static class {{listenerName}}Marshaller extends ListenerMarshaller
implements {{listenerName}}Listener
{
{{#methods}}
/** The method id used to dispatch {@link #{{method.name}}}
* responses. */
public static final int {{code}} = {{-index}};
// from interface {{listenerName}}Marshaller
public void {{method.name}} ({{getArgList}})
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, {{code}},
new Object[] { {{getWrappedArgList}} }, transport));
}
{{/methods}}
@Override // from InvocationMarshaller
{{#hasParameterizedMethodArgs}}
@SuppressWarnings("unchecked")
{{/hasParameterizedMethodArgs}}
public void dispatchResponse (int methodId, Object[] args)
{
switch (methodId) {
{{#methods}}
case {{code}}:
(({{listenerName}}Listener)listener).{{method.name}}(
{{getUnwrappedArgListAsListeners}});
return;
{{/methods}}
default:
super.dispatchResponse(methodId, args);
return;
}
}
}
{{/listeners}}
{{#methods}}
{{^-first}}
{{/-first}}
/** The method id used to dispatch {@link #{{method.name}}} requests. */
public static final int {{code}} = {{-index}};
// from interface {{name}}Service
public void {{method.name}} ({{getArgList}})
{
{{#listenerArgs}}
{{marshaller}} listener{{index}} = new {{marshaller}}();
listener{{index}}.listener = arg{{index}};
{{/listenerArgs}}
sendRequest(arg1, {{code}}, new Object[] {
{{#hasArgsSkipFirst}}
{{getWrappedArgListSkipFirst}}
{{/hasArgsSkipFirst}}
}{{transport}});
}
{{/methods}}
}
@@ -0,0 +1,37 @@
package {{package}} {
{{#imports}}
import {{this}};
{{/imports}}
/**
* Provides the implementation of the <code>{{name}}Service</code> 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
{
{{#methods}}
{{^-first}}
{{/-first}}
/** The method id used to dispatch <code>{{method.name}}</code> requests. */
public static const {{code}} :int = {{-index}};
// from interface {{name}}Service
public function {{method.name}} ({{getASArgListSkipFirst}}) :void
{
{{#listenerArgs}}
var listener{{indexSkipFirst}} :{{actionScriptMarshaller}} = new {{actionScriptMarshaller}}();
listener{{indexSkipFirst}}.listener = arg{{indexSkipFirst}};
{{/listenerArgs}}
sendRequest({{code}}, [
{{getASWrappedArgListSkipFirst}}
]);
}
{{/methods}}
}
}
@@ -0,0 +1,35 @@
package {{package}} {
{{#imports}}
import {{this}};
{{/imports}}
/**
* Marshalls instances of the {{name}}Service_{{listener.listenerName}}Marshaller interface.
*/
public class {{name}}Marshaller_{{listener.listenerName}}Marshaller
extends InvocationMarshaller_ListenerMarshaller
{
{{#listener.methods}}
/** The method id used to dispatch <code>{{method.name}}</code> responses. */
public static const {{code}} :int = {{-index}};
{{/listener.methods}}
// from InvocationMarshaller_ListenerMarshaller
override public function dispatchResponse (methodId :int, args :Array) :void
{
switch (methodId) {
{{#listener.methods}}
case {{code}}:
(listener as {{name}}Service_{{listener.listenerName}}Listener).{{method.name}}(
{{getASUnwrappedArgListAsListeners}});
return;
{{/listener.methods}}
default:
super.dispatchResponse(methodId, args);
return;
}
}
}
}
@@ -0,0 +1,26 @@
package {{package}};
import javax.annotation.Generated;
{{#imports}}
import {{this}};
{{/imports}}
/**
* Defines the server-side of the {@link {{name}}Service}.
*/
{{generated}}
public interface {{name}}Provider extends InvocationProvider
{
{{#methods}}
{{^-first}}
{{/-first}}
/**
* Handles a {@link {{name}}Service#{{method.name}}} request.
*/
void {{method.name}} (ClientObject caller{{#hasArgsSkipFirst}}, {{/hasArgsSkipFirst}}{{getArgListSkipFirst}}){{^listenerArgs.isEmpty}}
throws InvocationException{{/listenerArgs.isEmpty}};
{{/methods}}
}
@@ -0,0 +1,27 @@
package {{package}};
{{#imports}}
import {{this}};
{{/imports}}
/**
* Used to issue notifications to a {@link {{name}}Receiver} instance on a
* client.
*/
public class {{name}}Sender extends InvocationSender
{
{{#methods}}
/**
* Issues a notification that will result in a call to {@link
* {{name}}Receiver#{{method.name}}} on a client.
*/
public static void {{senderMethodName}} (
ClientObject target{{#hasArgs}}, {{/hasArgs}}{{getArgList}})
{
sendNotification(
target, {{name}}Decoder.RECEIVER_CODE, {{name}}Decoder.{{code}},
new Object[] { {{getWrappedArgList}} }{{transport}});
}
{{/methods}}
}
@@ -0,0 +1,20 @@
package {{package}} {
{{#imports}}
import {{this}};
{{/imports}}
/**
* An ActionScript version of the Java {{name}}Service interface.
*/
public interface {{name}}Service extends InvocationService
{
{{#methods}}
{{^-first}}
{{/-first}}
// from Java interface {{name}}Service
function {{method.name}} ({{getASArgListSkipFirst}}) :void;
{{/methods}}
}
}
@@ -0,0 +1,49 @@
package {{package}} {
{{#imports}}
import {{this}};
{{/imports}}
/**
* A functional adapter for the {{name}}Service_{{listener.listenerName}}Listener interface.
*/
public class {{name}}Service_{{listener.listenerName}}ListenerAdapter
implements {{name}}Service_{{listener.listenerName}}Listener
{
/**
* Creates a new {{name}} service {{listener.listenerName}} listener that will delegate to the
* given function(s). Any Function that is null will simply not be called.
*/
public function {{name}}Service_{{listener.listenerName}}ListenerAdapter (
{{adapterCtorArgs}}failed :Function)
{
{{#listener.methods}}
_{{method.name}} = {{method.name}};
{{/listener.methods}}
_failed = failed;
}
{{#listener.methods}}
// from Java {{name}}Service_{{listener.listenerName}}Listener
public function {{method.name}} ({{getASArgList}}) :void
{
if (_{{method.name}} != null) {
_{{method.name}}({{getASInvokeArgList}});
}
}
{{/listener.methods}}
// from InvocationService_InvocationListener
public function requestFailed (cause :String) :void
{
if (_failed != null) {
_failed(cause);
}
}
{{#listener.methods}}
protected var _{{method.name}} :Function;
{{/listener.methods}}
protected var _failed :Function;
}
}
@@ -0,0 +1,21 @@
package {{package}} {
{{#imports}}
import {{this}};
{{/imports}}
/**
* An ActionScript version of the Java {{name}}Service_{{listener.listenerName}}Listener interface.
*/
public interface {{name}}Service_{{listener.listenerName}}Listener
extends InvocationService_InvocationListener
{
{{#listener.methods}}
{{^-first}}
{{/-first}}
// from Java {{name}}Service_{{listener.listenerName}}Listener
function {{method.name}} ({{getASArgList}}) :void
{{/listener.methods}}
}
}
@@ -0,0 +1,45 @@
// GENERATED PREAMBLE START
{{header}}
package {{package}} {
{{#imports}}
import {{this}};
{{/imports}}
// GENERATED PREAMBLE END
// GENERATED CLASSDECL START
public class {{classname}} {{^extends.isEmpty}}extends {{#extends}}
{{/extends.isEmpty}}
{{^implements.isEmpty}}{{^extends.isEmpty}} {{/extends.isEmpty}}implements {{#implements}}
{{/implements.isEmpty}}
{
// GENERATED CLASSDECL END
// GENERATED STREAMING START
{{#fields}}
public var {{name}} :{{simpleType}};
{{/fields}}
{{#superclassStreamable}}override {{/superclassStreamable}}public function readObject (ins :ObjectInputStream) :void
{
{{#superclassStreamable}}
super.readObject(ins);
{{/superclassStreamable}}
{{#fields}}
{{name}} = ins.{{reader}};
{{/fields}}
}
{{#superclassStreamable}}override {{/superclassStreamable}}public function writeObject (out :ObjectOutputStream) :void
{
{{#superclassStreamable}}
super.writeObject(out);
{{/superclassStreamable}}
{{#fields}}
out.{{writer}};
{{/fields}}
}
// GENERATED STREAMING END
}
}