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:
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 <fileset> 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 <fileset> 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 <fileset> element which enumerates streamable class files.
|
||||
*/
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a <path> 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
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user