Factored Narya into two Maven modules: core and tools.

This will allow us to properly ship narya-tools.jar to Maven Central which is
necessary for Nenya and Vilya (and any other Narya-using project) to themselves
be built.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6776 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2012-02-03 21:25:13 +00:00
parent d73949838d
commit a7e0893191
781 changed files with 173 additions and 109 deletions
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.threerings</groupId>
<artifactId>narya-parent</artifactId>
<version>1.11-SNAPSHOT</version>
</parent>
<artifactId>narya-tools</artifactId>
<packaging>jar</packaging>
<name>Narya Tools</name>
<dependencies>
<!-- exported dependencies -->
<dependency>
<groupId>com.threerings</groupId>
<artifactId>narya</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.8.0.GA</version>
</dependency>
<!-- test/build dependencies -->
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,308 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.File;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.threerings.util.ActionScript;
import com.threerings.util.StreamableArrayList;
import com.threerings.util.StreamableHashMap;
import com.threerings.util.StreamableHashSet;
public class ActionScriptUtils
{
/**
* Adds an existing ActionScript file's imports to the given ImportSet.
* @param asFile a String containing the contents of an ActionScript file
*/
public static void addExistingImports (String asFile, ImportSet imports)
{
// Discover the location of the 'public class' declaration.
// We won't search past there.
Matcher m = AS_PUBLIC_CLASS_DECL.matcher(asFile);
int searchTo = asFile.length();
if (m.find()) {
searchTo = m.start();
}
m = AS_IMPORT.matcher(asFile.substring(0, searchTo));
while (m.find()) {
imports.add(m.group(3));
}
}
public static String addImportAndGetShortType (Class<?> type, boolean isField,
ImportSet imports)
{
String full = toActionScriptType(type, isField);
if (needsActionScriptImport(type, isField)) {
imports.add(full);
}
return Iterables.getLast(DOT_SPLITTER.split(full));
}
public static String toSimpleName (Class<?> type)
{
String name = type.getName().substring(type.getName().lastIndexOf(".") + 1);
// inner classes are not supported by ActionScript so we _
return name.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)) {
return "readField(" + toSimpleName(type) + ").value";
} else if (type.equals(Long.class)) {
return "readField(" + toSimpleName(type) + ")";
} 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 (isNaiveSet(type)) {
return "readField(SetStreamer.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 if (isNaiveSet(type)) {
return "writeField(" + name + ", SetStreamer.INSTANCE)";
} else {
return "writeObject(" + name + ")";
}
}
public static String toActionScriptType (Class<?> type, boolean isField)
{
if (type.isArray() || isNaiveList(type)) {
if (Byte.TYPE.equals(type.getComponentType())) {
return "flash.utils.ByteArray";
} else if (isField) {
return "com.threerings.io.TypedArray";
} else {
return "Array";
}
} else if (isNaiveMap(type)) {
return "com.threerings.util.Map";
} else if (isNaiveSet(type)) {
return "com.threerings.util.Set";
} 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 if (Cloneable.class.equals(type)) {
return "com.threerings.util.Cloneable";
} else if (Comparable.class.equals(type)) {
return "com.threerings.util.Comparable";
} else {
// inner classes are not supported by ActionScript so we _
return type.getName().replaceAll("\\$", "_");
}
}
/**
* Converts java types to their actionscript equivalents for ooo-style streaming.
*/
public static void convertBaseClasses (ImportSet imports)
{
// replace primitive types with OOO types (required for unboxing)
imports.replace("byte", "com.threerings.util.Byte");
imports.replace("boolean", "com.threerings.util.langBoolean");
imports.replace("[B", "flash.utils.ByteArray");
imports.replace("float", "com.threerings.util.Float");
imports.replace("long", "com.threerings.util.Long");
if (imports.removeAll("[*") > 0) {
imports.add("com.threerings.io.TypedArray");
}
// convert java primitive boxes to their ooo counterparts
imports.replace(Integer.class, "com.threerings.util.Integer");
// convert some java.util types to their ooo counterparts
imports.replace(Map.class, "com.threerings.util.Map");
// get rid of java.lang stuff and any remaining primitives
imports.removeGlobals();
// get rid of remaining arrays
imports.removeArrays();
}
public static File createActionScriptPath (File actionScriptRoot, Class<?> sclass)
{
// determine the path to the corresponding action script source file
String path = toActionScriptType(sclass, false).replace(".", File.separator);
return new File(actionScriptRoot, path + ".as");
}
public static boolean hasOmitAnnotation (Class<?> cclass)
{
// if we have an ActionScript(omit=true) annotation, skip this class
do {
ActionScript asa = cclass.getAnnotation(ActionScript.class);
if (asa != null && asa.omit()) {
// System.err.println("Skipping " + sclass.getName() + "...");
return true;
}
cclass = cclass.getSuperclass();
} while (cclass != null);
return false;
}
/** 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);
}
protected static boolean isNaiveSet (Class<?> type)
{
return Set.class.isAssignableFrom(type) && !type.equals(StreamableHashSet.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 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);
}
protected static final Splitter DOT_SPLITTER = Splitter.on('.');
protected static final Pattern AS_PUBLIC_CLASS_DECL = Pattern.compile("^public class(.*)$",
Pattern.MULTILINE);
protected static final Pattern AS_IMPORT = Pattern.compile("^(\\s*)import(\\s+)([^;]*);",
Pattern.MULTILINE);
}
@@ -0,0 +1,236 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.tools.ant.Project;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.primitives.Primitives;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.samskivert.util.StringUtil;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.OidList;
public class GenActionScriptStreamableTask extends GenTask
{
/**
* Configures the path to our ActionScript source files.
*/
public void setAsroot (File asroot)
{
_asroot = asroot;
}
@Override
protected void processClass (File javaSource, Class<?> sclass)
throws Exception
{
boolean streamable = Streamable.class.isAssignableFrom(sclass) || sclass.isEnum();
if (!streamable
|| InvocationMarshaller.class.isAssignableFrom(sclass)
|| Modifier.isInterface(sclass.getModifiers())
|| ActionScriptUtils.hasOmitAnnotation(sclass)) {
log("Skipping " + sclass.getName(), Project.MSG_VERBOSE);
return;
}
log("Generating " + sclass.getName(), Project.MSG_VERBOSE);
// Read the existing file in, if it exists
File outputLocation = ActionScriptUtils.createActionScriptPath(_asroot, sclass);
String existing = null;
if (outputLocation.exists()) {
existing = Files.toString(outputLocation, Charsets.UTF_8);
}
// Generate the current version of the streamable
StreamableClassRequirements reqs = new StreamableClassRequirements(sclass);
ImportSet imports = new ImportSet();
String extendsName = "";
if (sclass.isEnum()) {
imports.add("com.threerings.util.Enum");
extendsName = "Enum";
} else {
imports.add(ObjectInputStream.class.getName());
imports.add(ObjectOutputStream.class.getName());
}
if (!sclass.getSuperclass().equals(Object.class)) {
extendsName =
ActionScriptUtils.addImportAndGetShortType(sclass.getSuperclass(), false, imports);
}
// Read the existing file's imports in. Eclipse's import organization makes
// it a pain to keep additional imports out of the GENERATED PREAMBLE section of
// class, so we just merge all the imports we find.
if (existing != null) {
ActionScriptUtils.addExistingImports(existing, imports);
}
boolean isDObject = DObject.class.isAssignableFrom(sclass);
if (isDObject) {
imports.add("org.osflash.signals.Signal");
}
Set<String> implemented = Sets.newLinkedHashSet();
for (Class<?> iface : sclass.getInterfaces()) {
implemented.add(ActionScriptUtils.addImportAndGetShortType(iface, false, imports));
}
List<ASField> pubFields = Lists.newArrayList();
List<ASField> protFields = Lists.newArrayList();
for (Field f : reqs.streamedFields) {
int mods = f.getModifiers();
if (Modifier.isPublic(mods)) {
pubFields.add(new ASField(f, imports));
} else if (Modifier.isProtected(mods)) {
protFields.add(new ASField(f, imports));
} else {
// don't care about private
continue;
}
}
List<ASEnum> enumFields = Lists.newArrayList();
if (sclass.isEnum()) {
Object[] enums = sclass.getEnumConstants();
for (Object e : enums) {
enumFields.add(new ASEnum((Enum<?>)e));
}
}
imports.removeGlobals();
String template = sclass.isEnum() ? "enum_as.tmpl" : "streamable_as.tmpl";
String output = mergeTemplate("com/threerings/presents/tools/" + template,
"header", existing == null ? _header : "",
"package", sclass.getPackage().getName(),
"classname", ActionScriptUtils.toSimpleName(sclass),
"importGroups", imports.toGroups(),
"extends", extendsName,
"implements", Joiner.on(", ").join(implemented),
"superclassStreamable", reqs.superclassStreamable,
"pubFields", pubFields,
"enumFields", enumFields,
"protFields", protFields,
"dobject", isDObject);
if (existing != null) {
// Merge in the previously generated version
output = new GeneratedSourceMerger().merge(output, existing);
}
writeFile(outputLocation.getAbsolutePath(), output);
// generate inner enums
for (Class<?> inner : sclass.getDeclaredClasses()) {
if (inner.isEnum()) {
processClass(javaSource, inner);
}
}
}
protected static class ASField
{
public final String name;
public final String capitalName;
public final String simpleType;
public final String parameterTypes;
public final String reader;
public final String writer;
public final String dobjectField;
public boolean dset;
public boolean array;
public boolean oidList;
public boolean hasTypeParameters;
public ASField (Field f, ImportSet imports)
{
name = f.getName();
capitalName = StringUtil.capitalize(name);
dobjectField = StringUtil.unStudlyName(name).toUpperCase();
simpleType = ActionScriptUtils.addImportAndGetShortType(f.getType(), true, imports);
List<String> parameters = Lists.newLinkedList();
Type genType = f.getGenericType();
if (genType instanceof ParameterizedType) {
for (Type param : ((ParameterizedType) genType).getActualTypeArguments()) {
if (param instanceof Class) {
// Convert any box classes to primitives to avoid having to import them
Class<?> primitive = Primitives.unwrap((Class<?>) param);
parameters.add(ActionScriptUtils.addImportAndGetShortType(
primitive, false, imports));
}
}
}
if (!parameters.isEmpty()) {
parameterTypes = Joiner.on(", ").join(parameters);
hasTypeParameters = true;
} else {
parameterTypes = "";
}
// 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");
} else if (Set.class.isAssignableFrom(f.getType())) {
imports.add("com.threerings.io.streamers.SetStreamer");
} else if (DSet.class.isAssignableFrom(f.getType())) {
dset = true;
} else if (OidList.class.isAssignableFrom(f.getType())) {
oidList = true;
}
array = f.getType().isArray();
reader = ActionScriptUtils.toReadObject(f.getType());
writer = ActionScriptUtils.toWriteObject(f.getType(), name);
}
}
protected static class ASEnum
{
public final String name;
public ASEnum (Enum<?> e)
{
name = e.name();
}
}
/** The path to our ActionScript source files. */
protected File _asroot;
}
@@ -0,0 +1,186 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.io.File;
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 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
boolean array = ftype.isArray();
data.put("have_elem", array);
if (array) {
Class<?> etype = ftype.getComponentType();
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(EOL);
msection.append(EOL);
}
fsection.append(mergeTemplate(NAME_TMPL, data));
msection.append(mergeTemplate(BASE_TMPL + tname, data));
}
// now bolt everything back together into a class declaration
writeFile(source.getAbsolutePath(), sfile.generate(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,190 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.List;
import java.io.File;
import com.google.common.base.Preconditions;
import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.StringUtil;
import com.threerings.presents.client.InvocationDecoder;
import com.threerings.presents.client.InvocationReceiver;
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
{
/**
* Configures the path to our ActionScript source files.
*/
public void setAsroot (File asroot)
{
_asroot = asroot;
}
@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
Preconditions.checkArgument(rname.endsWith("Receiver"), "Cannot process '%s'. " +
"Receiver classes must be named SomethingReceiver.", rname);
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(createAndGatherImports(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);
generateDecoder(receiver, source, rname, rpackage, methods, imports,
StringUtil.md5hex(rpackage + "." + rname));
}
protected void generateSender (File source, String rname, String rpackage,
List<?> methods, ImportSet imports)
throws Exception
{
String name = rname.replace("Receiver", "");
String spackage = rpackage.replace(".client", ".server");
// construct our imports list
ImportSet impset = new ImportSet();
impset.addAll(imports);
impset.add(ClientObject.class);
impset.add(InvocationSender.class);
String dname = rname.replace("Receiver", "Decoder");
impset.add(rpackage + "." + dname);
impset.add(rpackage + "." + rname);
// determine the path to our sender file
String mpath = source.getPath();
mpath = mpath.replace("Receiver", "Sender");
mpath = replacePath(mpath, "/client/", "/server/");
writeTemplate(SENDER_TMPL, mpath,
"name", name,
"package", spackage,
"methods", methods,
"importGroups", impset.toGroups());
}
protected void generateDecoder (Class<?> receiver, File source, String rname, String rpackage,
List<ServiceMethod> methods, ImportSet imports,
String rcode) throws Exception
{
String name = rname.replace("Receiver", "");
// construct our imports list
ImportSet impset = new ImportSet();
impset.addAll(imports);
impset.add(InvocationDecoder.class);
// determine the path to our sender file
String mpath = source.getPath();
mpath = mpath.replace("Receiver", "Decoder");
writeTemplate(DECODER_TMPL, mpath,
"name", name,
"receiver_code", rcode,
"package", rpackage,
"methods", methods,
"importGroups", impset.toGroups());
if (_asroot == null) {
return;
}
// generate an ActionScript decoder
String sppath = rpackage.replace('.', File.separatorChar);
String aspath = _asroot + File.separator + sppath + File.separator + name + "Decoder.as";
writeTemplate(AS_DECODER_TMPL, aspath,
"name", name,
"receiver_code", rcode,
"package", rpackage,
"methods", methods,
"importGroups", impset.toGroups());
// ... and an ActionScript receiver
aspath = _asroot + File.separator + sppath + File.separator + rname + ".as";
impset.remove(InvocationDecoder.class);
impset.add(InvocationReceiver.class);
writeTemplate(AS_RECEIVER_TMPL, aspath,
"name", name,
"package", rpackage,
"methods", methods,
"importGroups", impset.toGroups());
}
/** The path to our ActionScript source files. */
protected File _asroot;
/** 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";
/** Specifies the path to the decoder template. */
protected static final String AS_DECODER_TMPL =
"com/threerings/presents/tools/decoder_as.tmpl";
/** Specifies the path to the decoder template. */
protected static final String AS_RECEIVER_TMPL =
"com/threerings/presents/tools/receiver_as.tmpl";
}
@@ -0,0 +1,700 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
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(createAndGatherImports(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 to output extra information when generating code.
*/
public void setVerbose (boolean verbose)
{
_verbose = verbose;
}
/**
* 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); // dispatchers are no longer needed
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(InvocationMarshaller.class);
imports.add("javax.annotation.Generated");
// We only add a type parameter for the caller ClientObject type if the service has one
if (sdesc.callerTypeSpecified) {
imports.add(sdesc.callerType);
}
// 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("typeParameters",
sdesc.callerTypeSpecified ? "<" + sdesc.callerType.getSimpleName() + ">" : "");
ctx.put("importGroups", imports.toGroups());
// determine the path to our marshaller file
String mpath = source.getPath();
mpath = mpath.replace("Service", "Marshaller");
mpath = replacePath(mpath, "/client/", "/data/");
writeTemplate(MARSHALLER_TMPL, mpath, 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(InvocationMarshaller.class);
// replace inner classes with action script equivalents
imports.translateInnerClasses();
// 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();
for (ServiceMethod method : sdesc.methods) {
method.gatherASWrappedArgListImports(imports);
}
// convert java bases and primitives
ActionScriptUtils.convertBaseClasses(imports);
// remove imports in our own package
imports.removeSamePackage(mpackage);
ctx.put("importGroups", imports.toGroups());
// 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";
writeTemplate(AS_MARSHALLER_TMPL, ampath, 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 java bases and primitives
ActionScriptUtils.convertBaseClasses(imports);
// remove imports in our own package
imports.removeSamePackage(mpackage);
ctx.put("importGroups", imports.toGroups());
ctx.put("listener", listener);
String aslpath = _asroot + File.separator + mppath +
File.separator + mname + "_" + listener.getListenerName() + "Marshaller.as";
writeTemplate(AS_LISTENER_MARSHALLER_TMPL, aslpath, 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(InvocationService.class);
// change imports of Foo$Bar to Foo_Bar
imports.translateInnerClasses();
// Boolean is built in
imports.remove("boolean");
// int is used for these
imports.remove("byte");
imports.remove("short");
imports.remove("char");
// convert java bases and primitives
ActionScriptUtils.convertBaseClasses(imports);
// remove imports in our own package
imports.removeSamePackage(sdesc.spackage);
ctx.put("importGroups", imports.toGroups());
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";
writeTemplate(AS_SERVICE_TMPL, aspath, 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();
ActionScriptUtils.convertBaseClasses(imports);
// remove imports in our own package
imports.removeSamePackage(sdesc.spackage);
ctx.put("importGroups", imports.toGroups());
ctx.put("listener", listener);
String aslpath = _asroot + File.separator + sppath + File.separator +
sname + "_" + listener.getListenerName() + "Listener.as";
writeTemplate(AS_LISTENER_SERVICE_TMPL, aslpath, ctx);
if (_aslistenerAdapters.contains(sname)) {
String aslapath = _asroot + File.separator + sppath + File.separator +
sname + "_" + listener.getListenerName() + "ListenerAdapter.as";
writeTemplate(AS_LISTENER_ADAPTER_SERVICE_TMPL, aslapath, 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(sdesc.callerType);
// 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/");
writeTemplate(DISPATCHER_TMPL, mpath,
"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();
if (!sdesc.methods.isEmpty()) {
imports.add(sdesc.callerType);
}
// import superclass and service
imports.add(InvocationProvider.class);
imports.add(sdesc.service);
imports.add("javax.annotation.Generated");
// 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/");
writeTemplate(PROVIDER_TMPL, mpath,
"name", name,
"generated", getGeneratedAnnotation(name),
"package", mpackage,
"methods", sdesc.methods,
"listeners", sdesc.listeners,
"callerType", sdesc.callerType.getSimpleName(),
"importGroups", imports.toGroups());
}
/**
* 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<?> callerType = ClientObject.class;
public boolean callerTypeSpecified;// True if callerType came from a type parameter
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;
Type[] genint = service.getGenericInterfaces();
if (genint.length > 0 && genint[0] instanceof ParameterizedType) {
callerType = (Class<?>)((ParameterizedType)genint[0]).getActualTypeArguments()[0];
callerTypeSpecified = true;
}
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(createAndGatherImports(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;
}
}
/** Show extra output if set. */
protected boolean _verbose;
/** 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,228 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.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 GenTask
{
/**
* Adds a nested &lt;fileset&gt; element which enumerates streamable source
* files.
*/
@Override
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.
*/
@Override
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() + "...");
writeFile(source.getAbsolutePath(), sfile.generate(null, methods.toString()));
}
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,274 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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 static com.google.common.base.Charsets.UTF_8;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
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.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.samskivert.io.StreamUtil;
import com.samskivert.mustache.Mustache;
public abstract class GenTask extends Task
{
/**
* Adds a nested &lt;fileset&gt; element which enumerates service declaration source files.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Configures 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());
}
}
/** 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());
}
/**
* Fails the build if generation would change files rather than generating
* code.
*/
public void setChecking (boolean checking)
{
_checking = checking;
}
/**
* Performs the actual work of the task.
*/
@Override
public void execute ()
{
if (_checking) {
log("Only checking if generation would change files", Project.MSG_VERBOSE);
}
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);
}
}
}
if (_checking && !_modifiedPaths.isEmpty()) {
throw new BuildException("Generation would produce changes!");
}
}
protected void writeTemplate (String templatePath, String outputPath, Object... data)
throws IOException
{
writeTemplate(templatePath, outputPath, createMap(data));
}
protected void writeTemplate (String templatePath, String outputPath, Map<String, Object> data)
throws IOException
{
String output = mergeTemplate(templatePath, data);
if (_header != null) {
output = convertEols(_header) + output;
}
writeFile(outputPath, output);
}
protected void writeFile (String outputPath, String output) throws IOException
{
File dest = new File(outputPath);
if (dest.exists()) {
if (wouldProduceSameFile(output, dest)) {
log("Skipping '" + outputPath + "' as it hasn't changed", Project.MSG_VERBOSE);
return;
}
} else if (!dest.getParentFile().exists() && !dest.getParentFile().mkdirs()) {
throw new BuildException("Unable to create directory for '" + dest.getAbsolutePath() + "'");
}
_modifiedPaths.add(outputPath);
if (_checking) {
log("Generating '" + outputPath + "' would have produced changes!", Project.MSG_ERR);
return;
}
log("Writing file " + outputPath, Project.MSG_VERBOSE);
new PrintWriter(dest, "UTF-8").append(output).close();
}
/**
* Returns true if the given string has the same content as the file, sans svn prop lines.
*/
protected boolean wouldProduceSameFile (String generated, File existing)
throws IOException
{
Iterator<String> generatedLines = Splitter.on(EOL).split(generated).iterator();
for (String prev : Files.readLines(existing, UTF_8)) {
if (!generatedLines.hasNext()) {
return false;
}
String cur = generatedLines.next();
if (!prev.equals(cur) && !(prev.startsWith("// $Id") && cur.startsWith("// $Id"))) {
return false;
}
}
// If the generated output ends with a newline, it'll have one more next from the splitter
// that reading the file doesn't produce.
if (generatedLines.hasNext()) {
return generatedLines.next().equals("") && !generatedLines.hasNext();
}
return true;
}
/**
* 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 IOException
{
return mergeTemplate(template, createMap(data));
}
/**
* 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 IOException
{
Reader reader =
new InputStreamReader(getClass().getClassLoader().getResourceAsStream(template), UTF_8);
return convertEols(Mustache.compiler().escapeHTML(false).compile(reader).execute(data));
}
protected Map<String, Object> createMap (Object... data)
{
Map<String, Object> ctx = Maps.newHashMap();
for (int ii = 0; ii < data.length; ii += 2) {
ctx.put((String)data[ii], data[ii+1]);
}
return ctx;
}
/**
* 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);
}
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);
}
}
protected String convertEols (String str)
{
return str.replace("\n", EOL);
}
protected static String EOL = System.getProperty("line.separator");
/** A list of filesets that contain java source to be processed. */
protected List<FileSet> _filesets = Lists.newArrayList();
/** Used to do our own classpath business. */
protected ClassLoader _cloader;
/** A header to put on all generated source files. */
protected String _header;
protected boolean _checking;
protected Set<String> _modifiedPaths = Sets.newHashSet();
}
@@ -0,0 +1,306 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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|final)\\s+)*(@?interface|class|enum)\\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 if (type instanceof Class<?>) {
return "(" + simpleName(type) + ")" + name;
} else {
return "this.<" + simpleName(type) + ">cast(" + 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)
{
return boxASArgumentAndGatherImports(clazz, name, new ImportSet());
}
/**
* Calculates the set of imports required for the code to box the given class in actionscript.
* For example, for <code>int</code>, <code>Integer.class</code> will be returned.
*/
public static ImportSet getASBoxImports (Class<?> clazz)
{
ImportSet imports = new ImportSet();
boxASArgumentAndGatherImports(clazz, "foo", imports);
return imports;
}
/**
* "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;
}
protected static String boxASArgumentAndGatherImports (Class<?> clazz, String name,
ImportSet imports)
{
if (clazz == Boolean.TYPE) {
imports.add(Boolean.class);
return "langBoolean.valueOf(" + name + ")";
} else if (clazz == Byte.TYPE) {
imports.add(Byte.class);
return "Byte.valueOf(" + name + ")";
} else if (clazz == Character.TYPE) {
imports.add(Character.class);
return "Character.valueOf(" + name + ")";
} else if (clazz == Short.TYPE) {
imports.add(Short.class);
return "Short.valueOf(" + name + ")";
} else if (clazz == Integer.TYPE) {
imports.add(Integer.class);
return "Integer.valueOf(" + name + ")";
} else if (clazz == Long.TYPE) {
imports.add(Long.class);
return name; // Long is left as is
} else if (clazz == Float.TYPE) {
imports.add(Float.class);
return "Float.valueOf(" + name + ")";
} else if (clazz == Double.TYPE) {
imports.add(Double.class);
return "Double.valueOf(" + name + ")";
} else {
imports.add(name);
return name;
}
}
}
@@ -0,0 +1,129 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* 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 <code>//
* GENERATED {name} START</code> and ends with <code>// GENERATED {name} END</code><p>
*
* If <code>previouslyGenerated</code> has a generated section replaced with <code>//
* GENERATED {name} DISABLED</code>, that section will no longer be updated.
*/
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, Section> sections = Maps.newLinkedHashMap();
Matcher m = _sectionDelimiter.matcher(newlyGenerated);
while (m.find()) {
Section section = extractGeneratedSection(m, newlyGenerated);
Preconditions.checkArgument(!sections.containsKey(section.name),
"Section '%s' used more than once", section.name);
sections.put(section.name, section);
}
// Merge with the previously generated source
StringBuilder merged = new StringBuilder();
m = _sectionDelimiter.matcher(previouslyGenerated);
int currentStart = 0;
while (m.find()) {
merged.append(previouslyGenerated.substring(currentStart, m.start()));
Section existingSection = extractGeneratedSection(m, previouslyGenerated);
Section newSection = sections.remove(existingSection.name);
if (newSection == null) {
// Allow generated sections to be dropped in the template, but warn in case
// something odd's happening
System.err.println("Dropping previously generated section '" + m.group(1)
+ "' that's no longer generated by the template");
} else if (existingSection.disabled) {
// If the existing code disables this generation, add that disabled comment
merged.append(existingSection.contents);
} else {
// Otherwise pop in the newly generated code in the place of what was there before
merged.append(newSection.contents);
}
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
for (Section newSection : sections.values()) {
System.err.println("Adding previously missing generated section '"
+ newSection.name + "' before the last non-generated text");
merged.append(newSection.contents);
}
// 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 Section extractGeneratedSection (Matcher m, String input)
{
int startIdx = m.start();
String name = m.group(1);
if (m.group(2).equals("DISABLED")) {
return new Section(name, input.substring(startIdx, m.end()), true);
}
Preconditions.checkArgument(m.group(2).equals("START"), "'%s' END without START",
name);
Preconditions.checkArgument(m.find(), "'%s' START without END", name);
String endName = m.group(1);
Preconditions.checkArgument(m.group(2).equals("END"),
"'%s' START after '%s' START", endName, name);
Preconditions.checkArgument(endName.equals(name),
"'%s' END after '%s' START", endName, name);
return new Section(name, input.substring(startIdx, m.end()), false);
}
protected static class Section {
public final String name, contents;
public final boolean disabled;
public Section (String name, String contents, boolean disabled) {
this.name = name;
this.contents = contents;
this.disabled = disabled;
}
}
protected final Pattern _sectionDelimiter =
Pattern.compile(" *// GENERATED (\\w+) (START|END|DISABLED)\r?\n");
}
@@ -0,0 +1,486 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import com.google.common.collect.ComparisonChain;
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 entirety. 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);
}
}
public void remove (String name)
{
_imports.remove(name);
}
/**
* 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);
}
}
/**
* Converts the set of imports to groups of class names, according to conventional package
* ordering and spacing. Within each group, sorting is alphabetical.
*/
public List<List<String>> toGroups ()
{
List<String> list = Lists.newArrayList(_imports);
Collections.sort(list, new Comparator<String>() {
public int compare (String class1, String class2) {
return ComparisonChain.start()
.compare(findImportGroup(class1), findImportGroup(class2))
.compare(class1, class2)
.result();
}
});
List<List<String>> result = Lists.newArrayList();
List<String> current = null;
int lastGroup = -2;
for (String imp : list) {
int group = findImportGroup(imp);
if (group != lastGroup) {
if (current == null || !current.isEmpty()) {
result.add(current = Lists.<String>newArrayList());
}
lastGroup = group;
}
current.add(imp);
}
return result;
}
/**
* 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('^');
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 static int findImportGroup (String imp)
{
String longest = null;
for (String prefix : IMPORT_GROUPS) {
if (!imp.startsWith(prefix)) {
continue;
}
if (longest == null || prefix.length() > longest.length()) {
longest = prefix;
}
}
return IMPORT_GROUPS.indexOf(longest);
}
protected HashSet<String> _imports = Sets.newHashSet();
protected List<String> _pushed = Lists.newArrayList();
protected static Pattern _splitter = Pattern.compile("\\*");
protected static List<String> IMPORT_GROUPS = Lists.newArrayList(
"flash",
"fl",
"java",
"javax",
"android",
"",
"com.samskivert",
"com.samskivert.swing",
"com.samskivert.servlet",
"com.samskivert.velocity",
"com.samskivert.jdbc",
"com.samskivert.depot",
"com.threerings.io",// Narya
"com.threerings.no",
"com.threerings.util",
"com.threerings.presents",
"com.threerings.crowd",
"com.threerings.admin",
"com.threerings.bureau",
"com.threering.tudey",
"com.threerings.display",// Aspirin
"com.threerings.geom",
"com.threerings.media",
"com.threerings.text",
"com.threerings.ui",
"com.threerings.flashbang",
"com.threerings.samsara",
"com.threerings.cast", // Nenya
"com.threerings.resource",
"com.threerings.miso",
"com.threerings.jme",
"com.threerings.openal",
"com.threerings.tools",
"com.threerings.lembas",
"com.threerings.parlor", // Vilya
"com.threerings.puzzle",
"com.threerings.whirled",
"com.threerings.micasa",
"com.threerings.stage",
"com.threerings.stats",
"com.threerings.orth",
"com.threerings",
"com.threerings.piracy",
"com.threerings.ppa",
"com.threerings.yohoho",
"com.threerings.who",
"com.threerings.projectx");
}
@@ -0,0 +1,358 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.tools;
import java.util.ArrayList;
import java.util.HashSet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.samskivert.io.StreamUtil;
import com.threerings.io.BasicStreamers;
import com.threerings.io.FieldMarshaller;
import com.threerings.io.Streamable;
/**
* Instruments compiled {@link Streamable} classes with public methods that can be used to stream
* protected and private members when running in a sandboxed JVM.
*/
public class InstrumentStreamableTask extends Task
{
/**
* Adds a nested &lt;fileset&gt; element which enumerates streamable class files.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Adds a &lt;path&gt; element which defines our classpath.
*/
public void addPath (Path path)
{
_paths.add(path);
}
/**
* Configures the directory into which we write our instrumented class files.
*/
public void setOutdir (File outdir)
{
_outdir = outdir;
}
@Override
public void execute ()
{
// configure our ClassPool with our classpath
for (Path path : _paths) {
for (String element : path.list()) {
try {
_pool.appendClassPath(element);
} catch (NotFoundException nfe) {
System.err.println("Invalid classpath entry [path=" + element + "]: " + nfe);
}
}
}
// instantiate streamable
try {
_streamable = _pool.get(Streamable.class.getName());
} catch (Exception e) {
throw new BuildException("Unable to load " + Streamable.class.getName() + ": " + e);
}
// now process the files
for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (String srcFile : srcFiles) {
processClass(new File(fromDir, srcFile));
}
}
}
/**
* Processes a class file.
*/
protected void processClass (File source)
{
CtClass clazz;
InputStream in = null;
try {
clazz = _pool.makeClass(in = new BufferedInputStream(new FileInputStream(source)));
} catch (IOException ioe) {
System.err.println("Failed to load " + source + ": " + ioe);
return;
} finally {
StreamUtil.close(in);
}
try {
if (clazz.subtypeOf(_streamable)) {
processStreamable(source, clazz);
}
} catch (NotFoundException nfe) {
System.err.println("Error processing class [class=" + clazz.getName() +
", error=" + nfe + "].");
}
}
/**
* Instruments the supplied {@link Streamable} implementing class.
*/
protected void processStreamable (File source, CtClass clazz)
throws NotFoundException
{
ArrayList<CtField> fields = Lists.newArrayList();
for (CtField field : clazz.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers) ||
!(Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers))) {
continue;
}
fields.add(field);
}
HashSet<String> methods = Sets.newHashSet();
for (CtMethod method : clazz.getMethods()) {
methods.add(method.getName());
}
int added = 0;
for (CtField field : fields) {
String rname = FieldMarshaller.getReaderMethodName(field.getName());
if (!methods.contains(rname)) {
String reader =
"public void " + rname + " (com.threerings.io.ObjectInputStream ins) {\n" +
// " throws java.io.IOException, java.lang.ClassNotFoundException\n" +
" " + getFieldReader(field) + "\n" +
"}";
// System.out.println("Adding reader " + clazz.getName() + ":\n" + reader);
try {
clazz.addMethod(CtNewMethod.make(reader, clazz));
added++;
} catch (CannotCompileException cce) {
System.err.println("Unable to compile reader [class=" + clazz.getName() +
", error=" + cce + "]:");
System.err.println(reader);
}
}
String wname = FieldMarshaller.getWriterMethodName(field.getName());
if (!methods.contains(wname)) {
String writer =
"public void " + wname + " (com.threerings.io.ObjectOutputStream out) {\n" +
// " throws java.io.IOException\n" +
" " + getFieldWriter(field) + "\n" +
"}";
// System.out.println("Adding writer " + clazz.getName() + ":\n" + writer);
try {
clazz.addMethod(CtNewMethod.make(writer, clazz));
added++;
} catch (CannotCompileException cce) {
System.err.println("Unable to compile writer [class=" + clazz.getName() +
", error=" + cce + "]:");
System.err.println(writer);
}
}
}
if (added > 0) {
try {
System.out.println("Instrumented '" + clazz.getName() + "'.");
clazz.writeFile(_outdir.getPath());
} catch (Exception e) {
System.err.println("Failed to write instrumented class [class=" + clazz +
", outdir=" + _outdir + "]: " + e);
}
}
}
protected String getFieldReader (CtField field)
throws NotFoundException
{
CtClass type = field.getType();
String name = field.getName();
if (type.getName().equals("java.lang.String")) {
return readWrap(field, name + " = ins.readUTF();");
} else if (type.equals(CtClass.booleanType) || type.getName().equals("java.lang.Boolean")) {
return readWrap(field, name + " = ins.readBoolean();");
} else if (type.equals(CtClass.byteType) || type.getName().equals("java.lang.Byte")) {
return readWrap(field, name + " = ins.readByte();");
} else if (type.equals(CtClass.shortType) || type.getName().equals("java.lang.Short")) {
return readWrap(field, name + " = ins.readShort();");
} else if (type.equals(CtClass.intType) || type.getName().equals("java.lang.Integer")) {
return readWrap(field, name + " = ins.readInt();");
} else if (type.equals(CtClass.longType) || type.getName().equals("java.lang.Long")) {
return readWrap(field, name + " = ins.readLong();");
} else if (type.equals(CtClass.floatType) || type.getName().equals("java.lang.Float")) {
return readWrap(field, name + " = ins.readFloat();");
} else if (type.equals(CtClass.doubleType) || type.getName().equals("java.lang.Double")) {
return readWrap(field, name + " = ins.readDouble();");
}
if (type.isArray()) {
CtClass ctype = type.getComponentType();
if (ctype.equals(CtClass.booleanType)) {
return readWrap(field, name + " = " + BSNAME + ".readBooleanArray(ins);");
} else if (ctype.equals(CtClass.byteType)) {
return readWrap(field, name + " = " + BSNAME + ".readByteArray(ins);");
} else if (ctype.equals(CtClass.shortType)) {
return readWrap(field, name + " = " + BSNAME + ".readShortArray(ins);");
} else if (ctype.equals(CtClass.intType)) {
return readWrap(field, name + " = " + BSNAME + ".readIntArray(ins);");
} else if (ctype.equals(CtClass.longType)) {
return readWrap(field, name + " = " + BSNAME + ".readLongArray(ins);");
} else if (ctype.equals(CtClass.floatType)) {
return readWrap(field, name + " = " + BSNAME + ".readFloat(ins);");
} else if (ctype.equals(CtClass.doubleType)) {
return readWrap(field, name + " = " + BSNAME + ".readDoubleArray(ins);");
} else if (ctype.getName().equals("java.lang.Object")) {
return readWrap(field, name + " = " + BSNAME + ".readObjectArray(ins);");
}
}
// no need to wrap streamable instances
return (name + " = (" + type.getName() + ")ins.readObject();");
}
protected String getFieldWriter (CtField field)
throws NotFoundException
{
CtClass type = field.getType();
String name = field.getName();
if (type.equals(CtClass.booleanType) || type.getName().equals("java.lang.Boolean")) {
return writeWrap(field, "out.writeBoolean(" + name + ");");
} else if (type.equals(CtClass.byteType) || type.getName().equals("java.lang.Byte")) {
return writeWrap(field, "out.writeByte(" + name + ");");
} else if (type.equals(CtClass.shortType) || type.getName().equals("java.lang.Short")) {
return writeWrap(field, "out.writeShort(" + name + ");");
} else if (type.equals(CtClass.intType) || type.getName().equals("java.lang.Integer")) {
return writeWrap(field, "out.writeInt(" + name + ");");
} else if (type.equals(CtClass.longType) || type.getName().equals("java.lang.Long")) {
return writeWrap(field, "out.writeLong(" + name + ");");
} else if (type.equals(CtClass.floatType) || type.getName().equals("java.lang.Float")) {
return writeWrap(field, "out.writeFloat(" + name + ");");
} else if (type.equals(CtClass.doubleType) || type.getName().equals("java.lang.Double")) {
return writeWrap(field, "out.writeDouble(" + name + ");");
} else if (type.getName().equals("java.lang.String")) {
return writeWrap(field, "out.writeUTF(" + name + ");");
}
if (type.isArray()) {
CtClass ctype = type.getComponentType();
if (ctype.equals(CtClass.booleanType)) {
return writeWrap(field, BSNAME + ".writeBooleanArray(out, " + name + ");");
} else if (ctype.equals(CtClass.byteType)) {
return writeWrap(field, BSNAME + ".writeByteArray(out, " + name + ");");
} else if (ctype.equals(CtClass.shortType)) {
return writeWrap(field, BSNAME + ".writeShortArray(out, " + name + ");");
} else if (ctype.equals(CtClass.intType)) {
return writeWrap(field, BSNAME + ".writeIntArray(out, " + name + ");");
} else if (ctype.equals(CtClass.longType)) {
return writeWrap(field, BSNAME + ".writeLongArray(out, " + name + ");");
} else if (ctype.equals(CtClass.floatType)) {
return writeWrap(field, BSNAME + ".writeFloat(out, " + name + ");");
} else if (ctype.equals(CtClass.doubleType)) {
return writeWrap(field, BSNAME + ".writeDoubleArray(out, " + name + ");");
} else if (ctype.getName().equals("java.lang.Object")) {
return writeWrap(field, BSNAME + ".writeObjectArray(out, " + name + ");");
}
}
// no need to wrap streamable instances
return "out.writeObject(" + name + ");";
}
protected String readWrap (CtField field, String body)
throws NotFoundException
{
if (field.getType().isPrimitive()) {
return body;
} else {
return "if (ins.readBoolean()) {\n" +
" " + body + "\n" +
" } else {\n" +
" " + field.getName() + " = null;\n" +
" }";
}
}
protected String writeWrap (CtField field, String body)
throws NotFoundException
{
if (field.getType().isPrimitive()) {
return body;
} else {
return "if (" + field.getName() + " == null) {\n" +
" out.writeBoolean(false);\n" +
" } else {\n" +
" out.writeBoolean(true);\n" +
" " + body + "\n" +
" }";
}
}
/** A list of filesets that contain Streamable class files. */
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
/** A list of paths that make up our classpath. */
protected ArrayList<Path> _paths = Lists.newArrayList();
/** The directory to which we write our instrumented class files. */
protected File _outdir;
/** Used to instrument class files. */
protected ClassPool _pool = ClassPool.getDefault();
/** Used to determine which classes implement {@link Streamable}. */
protected CtClass _streamable;
protected static final String BSNAME = BasicStreamers.class.getName();
}
@@ -0,0 +1,427 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.TypeVariable;
import java.lang.reflect.WildcardType;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.samskivert.util.Logger;
import com.samskivert.util.StringUtil;
import com.threerings.presents.annotation.TransportHint;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService.InvocationListener;
import com.threerings.presents.net.Transport;
/**
* 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;
}
protected int _index;
}
/**
* Creates a new service method and adds its basic imports to a set.
* @param method the method to create
* @param imports will be filled with the types required by the method
*/
public ServiceMethod createAndGatherImports (Method method, ImportSet imports)
{
ServiceMethod sm = new ServiceMethod(method);
sm.gatherImports(imports);
return sm;
}
/** 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
*/
public ServiceMethod (Method method) {
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));
}
}
}
public void gatherImports (ImportSet imports) {
// 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 typeParams () {
List<String> params = Lists.newArrayList();
for (Type type : method.getGenericParameterTypes()) {
collectTypeParams(type, params);
}
// the trailing space in '> ' is needed
return params.isEmpty() ? "" : StringUtil.toString(params, "<", "> ");
}
public String getArgList () {
StringBuilder buf = new StringBuilder();
Type[] ptypes = method.getGenericParameterTypes();
for (int ii = 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(ii+1);
}
return buf.toString();
}
public String getASArgList () {
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+1).append(" :");
buf.append(GenUtil.simpleASName(args[ii]));
}
return buf.toString();
}
public String getASInvokeArgList () {
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+1);
}
return buf.toString();
}
public String getWrappedArgList () {
StringBuilder buf = new StringBuilder();
Class<?>[] args = method.getParameterTypes();
for (int ii = 0; ii < args.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append(boxArgument(args[ii], ii+1));
}
return buf.toString();
}
public void gatherASWrappedArgListImports (ImportSet set) {
Class<?>[] args = method.getParameterTypes();
for (int ii = 0; ii < args.length; ii++) {
set.addAll(GenUtil.getASBoxImports(args[ii]));
}
}
public String getASWrappedArgList () {
StringBuilder buf = new StringBuilder();
Class<?>[] args = method.getParameterTypes();
for (int ii = 0; ii < args.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
String index = String.valueOf(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 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 = 0; ii < ptypes.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append(unboxArgument(ptypes[ii], ii, 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 = 0; ii < args.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
String arg;
int argidx = ii;
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 if (type instanceof TypeVariable) {
// nothing needed
} else {
System.err.println(Logger.format(
"Unhandled Type in adding imports for a service", "type", type, "typeClass",
type.getClass()));
}
}
protected void collectTypeParams (Type type, List<String> params) {
if (type instanceof TypeVariable) {
String tvar = ((TypeVariable<?>)type).getName();
if (!params.contains(tvar)) params.add(tvar);
} else if (type instanceof ParameterizedType) {
for (Type pt : ((ParameterizedType)type).getActualTypeArguments()) {
collectTypeParams(pt, params);
}
} else if (type instanceof WildcardType) {
for (Type lb : ((WildcardType)type).getLowerBounds()) {
collectTypeParams(lb, params);
}
for (Type ub : ((WildcardType)type).getUpperBounds()) {
collectTypeParams(ub, params);
}
} // else nada
}
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 + "]");
}
}
}
@Override
public void execute ()
{
// resolve the InvocationListener and Client classes using our classloader
_ilistener = loadClass(InvocationListener.class.getName());
_iclient = loadClass(Client.class.getName());
super.execute();
}
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));
}
/** {@link InvocationListener} resolved with the proper classloader so that we can compare it
* to loaded derived classes. */
protected Class<?> _ilistener;
/** {@link Client} resolved with the proper classloader so that we can compare it to loaded
* derived classes. */
protected Class<?> _iclient;
}
@@ -0,0 +1,51 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.security.KeyPair;
import com.threerings.presents.util.SecureUtil;
/**
* Generates a RSA public/private key pair and outputs them in a format suitable for a properties
* file.
*/
public class KeyPairGen
{
public static void main (String[] args)
{
if (args.length != 1) {
System.err.println("Usage: KeyPairGen bits");
System.exit(-1);
}
try {
int bits = Integer.parseInt(args[0]);
KeyPair kp = SecureUtil.genRSAKeyPair(bits);
System.out.println("key.public = " + SecureUtil.RSAKeyToString(kp.getPublic()));
System.out.println("key.private = " + SecureUtil.RSAKeyToString(kp.getPrivate()));
} catch (NumberFormatException nfe) {
System.err.println("Usage: KeyPairGen bits");
System.exit(-1);
}
}
}
@@ -0,0 +1,275 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.IOException;
import java.io.StringWriter;
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 String generate (String fsection, String msection)
throws IOException
{
StringWriter writer = new StringWriter();
BufferedWriter bout = new BufferedWriter(writer);
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();
return writer.toString();
}
/** 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-2012 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,85 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import java.lang.reflect.Type;
import java.util.List;
import com.google.common.collect.Lists;
import com.threerings.presents.tools.InvocationTask.ServiceMethod;
public class CPPArgBuilder
{
public String getArguments (ServiceMethod meth)
{
return getArguments(meth, "");
}
public String getArguments (ServiceMethod meth, String prefix)
{
StringBuilder buf = new StringBuilder(prefix);
Type[] ptypes = meth.method.getGenericParameterTypes();
for (int ii = 0; ii < ptypes.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append(CPPUtil.getCPPType(ptypes[ii])).append(" arg").append(ii+1);
}
return buf.toString();
}
public List<String> getArgumentNames (ServiceMethod meth)
{
Type[] ptypes = meth.method.getGenericParameterTypes();
List<String> args = Lists.newArrayListWithCapacity(ptypes.length);
for (int ii = 0; ii < ptypes.length; ii++) {
args.add("arg" + (ii+1));
}
return args;
}
public String getArgumentsFromVector (ServiceMethod meth)
{
StringBuilder buf = new StringBuilder();
Type[] ptypes = meth.method.getGenericParameterTypes();
for (int ii = 0; ii < ptypes.length; ii++) {
if (buf.length() > 0) {
buf.append(", ");
}
CPPType type = new CPPType(ptypes[ii]);
buf.append(type.getCastFromStreamable("args[" + ii + "]"));
}
return buf.toString();
}
public List<String> getServiceArguments (ServiceMethod meth)
{
Type[] ptypes = meth.method.getGenericParameterTypes();
List<String> args = Lists.newArrayListWithCapacity(ptypes.length);
for (int ii = 0; ii < ptypes.length; ii++) {
args.add(new CPPType(ptypes[ii]).getAsStreamable("arg" + (ii+1)));
}
return args;
}
}
@@ -0,0 +1,49 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import java.lang.reflect.Field;
import java.io.IOException;
public class CPPField
{
public final String name;
public final CPPType type;
public final String reader;
public final String writer;
public CPPField (Field f)
throws IOException
{
type = new CPPType(f.getGenericType());
name = f.getName().startsWith("_") ? f.getName().substring(1) : f.getName();
if (type.fixed != null) {
reader = type.cast + "(in.read" + type.interpreter + "(" + type.fixed + "))";
} else if (type.interpreter.equals("Field")) {
reader = "in.readField< " + type.getWithoutShared() + " >()";
} else {
reader = type.cast + "(in.read" + type.interpreter + "())";
}
writer = "out.write" + type.interpreter + "(" + name
+ (type.fixed == null ? "" : ", " + type.fixed) + ")";
}
}
@@ -0,0 +1,177 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.threerings.io.Streamable;
import com.threerings.presents.dobj.DSet;
public class CPPType
{
public static final String JAVA_LIST_FIXED = "JAVA_LIST_NAME()";
/** The full cpp type. */
public final String type;
/** The method on ObjectInputStream and ObjectOutputStream to interpret this type. */
public final String interpreter;
/** CPP code to cast from this object to its reader type. */
public final String cast;
/** If this is a fixed type and thereby doesn't need a type encoding on the wire. */
public final String fixed;
/** Another type embedded in this type, or null if it doesn't have an embedded type. */
public final CPPType dependent;
public final String representationImport;
public final Class<?> rawType;
public final boolean primitive;
public CPPType (Type javaType)
{
if (javaType instanceof ParameterizedType) {
rawType = (Class<?>)((ParameterizedType)javaType).getRawType();
} else if (javaType instanceof TypeVariable<?>) {
rawType = (Class<?>)((TypeVariable<?>)javaType).getBounds()[0];
} else {
rawType = (Class<?>)javaType;
}
if (rawType.equals(List.class)) {
fixed = JAVA_LIST_FIXED;
} else {
fixed = null;
}
if (rawType.isArray()) {
Class<?> componentType = rawType.getComponentType();
dependent = new CPPType(componentType);
type = "Shared< std::vector< " + dependent.type + " > >";
interpreter = componentType.equals(Object.class) ||
componentType.isPrimitive() ? "Field" : "Object";
representationImport = null;
} else {
type = CPPUtil.getCPPType(javaType);
interpreter = getCPPInterpreter(rawType);
if (javaType instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType)javaType).getActualTypeArguments();
if (rawType.equals(Comparable.class)) {
dependent = new CPPType(Streamable.class);
representationImport = "presents/Streamable.h";
} else if (rawType == List.class) {
Class<?> param;
if (typeArguments[0] instanceof ParameterizedType) {
param = (Class<?>)((ParameterizedType)typeArguments[0]).getRawType();
} else {
param = (Class<?>)typeArguments[0];
}
if (!Streamable.class.isAssignableFrom(param)) {
throw new IllegalArgumentException(
"Lists may only contain Streamables in C++, not '" + typeArguments[0]
+ "'");
}
dependent = new CPPType(param);
representationImport = null;
} else if (rawType.equals(DSet.class)) {
dependent = null;
representationImport = CPPUtil.makePath(rawType, ".h");
} else {
throw new IllegalArgumentException("Don't know how to handle " + rawType);
}
} else if (javaType instanceof TypeVariable<?>
|| javaType.equals(com.threerings.presents.dobj.DSet.Entry.class)) {
dependent = new CPPType(Streamable.class);
representationImport = "presents/Streamable.h";
} else {
if (!Streamable.class.equals(rawType) && Streamable.class.isAssignableFrom(rawType)) {
representationImport = CPPUtil.makePath(rawType, ".h");
} else {
representationImport = null;
}
dependent = null;
}
}
primitive = rawType.isPrimitive();
Matcher m = TYPE_EXTRACT.matcher(type);
cast = m.matches() ? "boost::static_pointer_cast<" + m.group(1) + ">" : type;
}
protected String getCPPInterpreter (Class<?> ftype)
{
if (ftype.equals(String.class) || ftype.equals(List.class)) {
return "Field";
} else if (ftype.equals(Boolean.TYPE)) {
return "Boolean";
} else if (ftype.equals(Byte.TYPE)) {
return "Byte";
} else if (ftype.equals(Short.TYPE)) {
return "Short";
} else if (ftype.equals(Integer.TYPE)) {
return "Int";
} else if (ftype.equals(Long.TYPE)) {
return "Long";
} else if (ftype.equals(Float.TYPE)) {
return "Float";
} else if (ftype.equals(Double.TYPE)) {
return "Double";
} else {
return "Object";
}
}
public String getCastFromStreamable (String name)
{
if (primitive) {
return "boost::static_pointer_cast<presents::box::Boxed" + interpreter + ">(" + name + ")->value";
}
return cast + "(" + name + ")";
}
public String getAsStreamable (String name)
{
if (primitive) {
return "presents::box::Boxed" + interpreter + "::createShared(" + name + ")";
}
return name;
}
public String getWithoutShared ()
{
Matcher m = TYPE_EXTRACT.matcher(type);
return m.matches() ? m.group(1) : type;
}
protected static final Pattern TYPE_EXTRACT = Pattern.compile("Shared<(.*)>");
}
@@ -0,0 +1,142 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import java.io.File;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.DSet;
public class CPPUtil
{
public static String getCPPType (Type ftype)
{
if (ftype.equals(com.threerings.presents.dobj.DSet.Entry.class)) {
return "Shared<Streamable>";
}
if (ftype instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType)ftype).getActualTypeArguments();
Type raw = ((ParameterizedType)ftype).getRawType();
if (raw.equals(Comparable.class)) {
return "Shared<Streamable>";
} else if (raw.equals(List.class)) {
return "Shared< std::vector< " + getCPPType(typeArguments[0]) + " > >";
} else if (raw.equals(DSet.class) || raw.equals(InvocationMarshaller.class)) {
ftype = raw;
} else {
throw new IllegalArgumentException("Don't know how to handle " + raw);
}
}
if (ftype.equals(Boolean.class) || ftype.equals(Byte.class) || ftype.equals(Short.class)
|| ftype.equals(Integer.class) || ftype.equals(Long.class)
|| ftype.equals(Float.class) || ftype.equals(Double.class)) {
throw new IllegalArgumentException("Presents can't handle boxed types in C++");
}
if (ftype.equals(String.class)) {
return "Shared<utf8>";
} else if (ftype.equals(Boolean.TYPE)) {
return "bool";
} else if (ftype.equals(Byte.TYPE)) {
return "int8";
} else if (ftype.equals(Short.TYPE)) {
return "int16";
} else if (ftype.equals(Integer.TYPE)) {
return "int32";
} else if (ftype.equals(Long.TYPE)) {
return "int64";
} else if (ftype.equals(Float.TYPE)) {
return "float";
} else if (ftype.equals(Double.TYPE)) {
return "double";
} else if (ftype.equals(Object.class) || ftype instanceof TypeVariable<?>) {
return "Shared<Streamable>";
} else {
return "Shared<" + makeCPPName((Class<?>)ftype) + ">";
}
}
public static String makeCPPName (Class<?> sclass)
{
return makeCPPName(makeNamespaces(sclass), sclass.getSimpleName());
}
public static String makeCPPName (List<String> namespaces, String className)
{
return Joiner.on("::").join(namespaces) + "::" + className;
}
public static String makePath (Class<?> klass, String ext)
{
return makePath(makeNamespaces(klass), klass.getSimpleName(), ext);
}
public static String makePath (List<String> namespaces, String className, String ext)
{
return Joiner.on(File.separator).join(namespaces) + File.separator + className+ ext;
}
public static String makePath (File root, Class<?> klass, String ext)
{
return new File(root, makePath(klass, ext)).getAbsolutePath();
}
public static String makePath (File root, List<String> namespaces, String className, String ext)
{
return new File(root, makePath(namespaces, className, ext)).getAbsolutePath();
}
public static List<String> makeNamespaces (String pack)
{
Iterable<String> split = Splitter.on(".").split(pack);
List<String> segs = Lists.newArrayList(split);
if (segs.size() > 1 && segs.get(0).equals("com") && segs.get(1).equals("threerings")) {
segs.remove(0);
segs.remove(0);
}
return segs;
}
public static List<String> makeNamespaces (Class<?> sclass)
{
if (sclass.getPackage() == null) {
return Collections.emptyList();
} else {
return makeNamespaces(sclass.getPackage().getName());
}
}
public static String makeNamespace (Class<?> sclass)
{
return Joiner.on("::").join(makeNamespaces(sclass));
}
}
@@ -0,0 +1,100 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import static com.threerings.presents.tools.cpp.CPPUtil.makePath;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.File;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.threerings.presents.tools.GenReceiverTask;
import com.threerings.presents.tools.ImportSet;
public class GenCPPReceiverTask extends GenReceiverTask
{
public void setCpproot (File asroot)
{
_cpproot = asroot;
}
@Override
protected void generateDecoder (Class<?> receiver, File source, String rname, String rpackage,
List<ServiceMethod> methods, ImportSet imports, String rcode)
throws Exception
{
String dname = rname.replace("Receiver", "Decoder");
Map<String, Object> ctx = Maps.newHashMap();
List<String> namespaces = CPPUtil.makeNamespaces(receiver);
ctx.put("receiverName", rname);
ctx.put("decoderName", dname);
ctx.put("namespaces", namespaces);
ctx.put("namespace", CPPUtil.makeNamespace(receiver));
ctx.put("package", rpackage);
ctx.put("methods", MethodDescriptor.from(methods));
ctx.put("receiverCode", rcode);
ctx.put("argbuilder", new CPPArgBuilder());
writeTemplate(DECODER_HEADER_TMPL, makePath(_cpproot, namespaces, dname, ".h"), ctx);
Set<String> receiverHeaderIncludes = Sets.newTreeSet();
Set<String> decoderImplIncludes = Sets.newTreeSet();
for (ServiceMethod meth : methods) {
for (Type type : meth.method.getGenericParameterTypes()) {
CPPType cppType = new CPPType(type);
if (cppType.primitive) {
decoderImplIncludes.add("presents/box/Boxed" + cppType.interpreter + ".h");
}
while (cppType != null) {
if (cppType.representationImport != null) {
receiverHeaderIncludes.add(cppType.representationImport);
}
cppType = cppType.dependent;
}
}
}
ctx.put("includes", decoderImplIncludes);
writeTemplate(DECODER_CPP_TMPL, makePath(_cpproot, namespaces, dname, ".cpp"), ctx);
ctx.put("includes", receiverHeaderIncludes);
writeTemplate(RECEIVER_HEADER_TMPL, makePath(_cpproot, namespaces, rname, ".h"), ctx);
super.generateDecoder(receiver, source, rname, rpackage, methods, imports, rcode);
}
protected File _cpproot;
protected static final String RECEIVER_HEADER_TMPL =
"com/threerings/presents/tools/cpp/receiver_h.mustache";
protected static final String DECODER_HEADER_TMPL =
"com/threerings/presents/tools/cpp/decoder_h.mustache";
protected static final String DECODER_CPP_TMPL =
"com/threerings/presents/tools/cpp/decoder_cpp.mustache";
}
@@ -0,0 +1,93 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import java.lang.reflect.Type;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.threerings.presents.tools.GenServiceTask;
import static com.threerings.presents.tools.cpp.CPPUtil.makeNamespaces;
import static com.threerings.presents.tools.cpp.CPPUtil.makePath;
public class GenCPPServiceTask extends GenServiceTask
{
public void setCpproot (File asroot)
{
_cpproot = asroot;
}
@Override
protected void generateMarshaller (File source, ServiceDescription sdesc)
throws Exception
{
String name = sdesc.sname.replace("Service", "Marshaller");
String mpackage = sdesc.spackage.replace(".client", ".data");
List<String> namespaces = makeNamespaces(mpackage);
Map<String, Object> ctx = Maps.newHashMap();
ctx.put("name", name);
ctx.put("javaName", mpackage + "." + name);
ctx.put("namespaces", namespaces);
ctx.put("namespace", Joiner.on("::").join(namespaces));
ctx.put("methods", MethodDescriptor.from(sdesc.methods));
ctx.put("listeners", sdesc.listeners);
ctx.put("argbuilder", new CPPArgBuilder());
Set<String> includes = Sets.newTreeSet();
Set<String> implIncludes = Sets.newTreeSet();
for (ServiceMethod meth : sdesc.methods) {
for (Type type : meth.method.getGenericParameterTypes()) {
CPPType cppType = new CPPType(type);
if (cppType.primitive) {
implIncludes.add("presents/box/Boxed" + cppType.interpreter + ".h");
}
while (cppType != null) {
if (cppType.representationImport != null) {
includes.add(cppType.representationImport);
}
cppType = cppType.dependent;
}
}
}
ctx.put("includes", implIncludes);
writeTemplate(CPP_TMPL, makePath(_cpproot, namespaces, name, ".cpp"), ctx);
ctx.put("includes", includes);
writeTemplate(HEADER_TMPL, makePath(_cpproot, namespaces, name, ".h"), ctx);
super.generateMarshaller(source, sdesc);
}
protected File _cpproot;
protected static final String HEADER_TMPL =
"com/threerings/presents/tools/cpp/marshaller_h.mustache";
protected static final String CPP_TMPL =
"com/threerings/presents/tools/cpp/marshaller_cpp.mustache";
}
@@ -0,0 +1,182 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
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.File;
import java.io.IOException;
import org.apache.tools.ant.BuildException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.io.Streamable;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.net.Message;
import com.threerings.presents.tools.GenTask;
import static com.threerings.presents.tools.cpp.CPPUtil.makeCPPName;
import static com.threerings.presents.tools.cpp.CPPUtil.makeNamespaces;
import static com.threerings.presents.tools.cpp.CPPUtil.makePath;
public class GenCPPStreamableTask extends GenTask
{
public class Generate {
public void setClass (String name)
{
_toProcess.add(loadClass(name));
}
}
public Generate createGenerate ()
{
return new Generate();
}
public void setCpproot (File asroot)
{
_cpproot = asroot;
}
@Override
public void execute ()
{
for (Class<?> klass : _toProcess) {
processClass(null, klass);
}
super.execute();
}
@Override
protected void processClass (File fn, Class<?> sclass)
{
try {
processClass(sclass);
} catch (IOException e) {
throw new BuildException(e);
}
}
/**
* Processes a resolved Streamable class instance.
*/
protected void processClass (Class<?> sclass)
throws IOException
{
if (!Streamable.class.isAssignableFrom(sclass) ||
((sclass.getModifiers() & Modifier.INTERFACE) != 0) ||
DSet.class.equals(sclass) ||
(InvocationMarshaller.class.isAssignableFrom(sclass) && !InvocationMarshaller.class.equals(sclass))) {
// System.err.println("Skipping " + sclass.getName() + "...");
return;
}
System.err.println("Generating " + sclass.getName());
// see if our parent also implements Streamable
boolean needSuper = Streamable.class.isAssignableFrom(sclass.getSuperclass()) &&
!NONSUPER.contains(sclass.getSuperclass());
Map<String, Object> ctx = Maps.newHashMap();
ctx.put("superclassStreamable", needSuper);
ctx.put("name", sclass.getSimpleName());
ctx.put("namespaces", makeNamespaces(sclass));
ctx.put("javaName", sclass.getName());
ctx.put("namespace", CPPUtil.makeNamespace(sclass));
Set<String> headerIncludes = Sets.newTreeSet();
Set<String> implIncludes = Sets.newTreeSet();
if (needSuper) {
ctx.put("super", makeCPPName(sclass.getSuperclass()));
addInclude(sclass.getSuperclass(), headerIncludes);
} else {
ctx.put("super", "Streamable");
headerIncludes.add("presents/Streamable.h");
}
List<CPPField> fields = Lists.newArrayList();
ctx.put("fields", fields);
for (Field field : sclass.getDeclaredFields()) {
int mods = field.getModifiers();
if (!Modifier.isStatic(mods) && !Modifier.isTransient(mods)) {
CPPField cppField = new CPPField(field);
fields.add(cppField);
CPPType type = cppField.type;
while (type != null) {
if (CPPType.JAVA_LIST_FIXED.equals(type.fixed)) {
implIncludes.add("presents/Streamer.h");
}
if (type.representationImport != null) {
headerIncludes.add(type.representationImport);
}
type = type.dependent;
}
}
}
String inStream, outStream;
if (fields.isEmpty() && !needSuper) {
inStream = "/*in*/";
outStream = "/*out*/";
} else {
inStream = "in";
outStream = "out";
}
ctx.put("inStreamArg", inStream);
ctx.put("outStreamArg", outStream);
// now write all that out to the target source file
ctx.put("includes", headerIncludes);
writeTemplate(HEADER_TMPL, makePath(_cpproot, sclass, ".h"), ctx);
ctx.put("includes", implIncludes);
writeTemplate(CPP_TMPL, makePath(_cpproot, sclass, ".cpp"), ctx);
}
protected static void addInclude (Class<?> ftype, Set<String> includes)
{
includes.add(makePath(ftype, ".h"));
}
protected File _cpproot;
protected Set<Class<?>> _toProcess = Sets.newHashSet();
protected static final Set<Class<?>> NONSUPER = Sets.newHashSet();
static {
NONSUPER.add(Message.class);
NONSUPER.add(SimpleStreamableObject.class);
}
protected static final String HEADER_TMPL =
"com/threerings/presents/tools/cpp/streamable_h.mustache";
protected static final String CPP_TMPL =
"com/threerings/presents/tools/cpp/streamable_cpp.mustache";
}
@@ -0,0 +1,54 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.cpp;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.threerings.presents.tools.InvocationTask.ServiceMethod;
public class MethodDescriptor
{
public static List<MethodDescriptor> from(List<ServiceMethod> methods) {
return Lists.transform(methods, new Function<ServiceMethod, MethodDescriptor>() {
public MethodDescriptor apply (ServiceMethod from) {
return new MethodDescriptor(from);
}});
}
public final String methodName;
public final String vectorArguments;
public final String arguments;
public final String clientArguments;
public final List<String> serviceArguments;
public MethodDescriptor(ServiceMethod methodSource) {
methodName = methodSource.method.getName();
vectorArguments = new CPPArgBuilder().getArgumentsFromVector(methodSource);
arguments = new CPPArgBuilder().getArguments(methodSource);
clientArguments = new CPPArgBuilder().getArguments(
methodSource, "Shared<presents::PresentsClient> client");
serviceArguments = new CPPArgBuilder().getServiceArguments(methodSource);
}
}
@@ -0,0 +1,28 @@
#include "presents/stable.h"
#include "{{decoderName}}.h"
#include "{{receiverName}}.h"
{{#includes}}
#include "{{this}}"
{{/includes}}
using namespace {{namespace}};
{{decoderName}}::{{decoderName}} (Shared<{{receiverName}}> receiver) :
InvocationDecoder("{{receiverCode}}"),
_receiver(receiver)
{
}
void {{decoderName}}::dispatchNotification(int8 methodId, const std::vector< Shared<Streamable> >& args)
{
switch(methodId) {
{{#methods}}
case {{-index}}:
_receiver->{{methodName}} ( {{vectorArguments}} );
return;
{{/methods}}
default:
LOG_INFO("{{decoderName}} got unknown method: %d", methodId);
return;
}
}
@@ -0,0 +1,20 @@
#pragma once
#include "presents/InvocationDecoder.h"
{{#namespaces}}
namespace {{this}} { {{/namespaces}}
class {{receiverName}};
class {{decoderName}} : public presents::InvocationDecoder {
public:
{{decoderName}}(Shared<{{receiverName}}> receiver);
void dispatchNotification(int8 methodId, const std::vector< Shared<Streamable> >& args);
protected:
Shared<{{receiverName}}> _receiver;
};
{{#namespaces}}
}{{/namespaces}}
@@ -0,0 +1,28 @@
#include "presents/stable.h"
#include "{{name}}.h"
#include "presents/PresentsClient.h"
{{#includes}}
#include "{{this}}"
{{/includes}}
using namespace {{namespace}};
DEFINE_STREAMABLE("{{javaName}}", {{name}});
{{#methods}}
void {{name}}::{{methodName}} ({{clientArguments}})
{
typedef std::vector< Shared<Streamable> > StreamableList;
Shared<StreamableList> args(new StreamableList);
{{#serviceArguments}}
args->push_back({{this}});
{{/serviceArguments}}
args->push_back(getSharedThis());
client->sendRequest(invOid, invCode, {{-index}}, args);
}
{{/methods}}
Shared<{{name}}> {{name}}::getSharedThis()
{
return shared_from_this();
}
@@ -0,0 +1,28 @@
#pragma once
#include "presents/Streamable.h"
#include "presents/ObjectInputStream.h"
#include "presents/ObjectOutputStream.h"
#include "presents/data/InvocationMarshaller.h"
#include "presents/streamers/StreamableStreamer.h"
#include <boost/enable_shared_from_this.hpp>
{{#includes}}
#include "{{this}}"
{{/includes}}
{{#namespaces}}namespace {{this}} { {{/namespaces}}
class {{name}} : public presents::data::InvocationMarshaller, public boost::enable_shared_from_this<{{name}}> {
public:
DECLARE_STREAMABLE();
virtual ~{{name}} () {}
{{#methods}}
void {{methodName}} ({{clientArguments}});
{{/methods}}
protected:
Shared<{{name}}> getSharedThis();
};
{{#namespaces}}}{{/namespaces}}
@@ -0,0 +1,21 @@
#pragma once
#include "{{decoderName}}.h"
{{#includes}}
#include "{{this}}"
{{/includes}}
{{#namespaces}}namespace {{this}} { {{/namespaces}}
class {{receiverName}} {
public:
typedef {{decoderName}} Decoder;
{{#methods}}
virtual void {{methodName}} ({{arguments}}) = 0;
{{/methods}}
};
{{#namespaces}}
}{{/namespaces}}
@@ -0,0 +1,29 @@
#include "presents/stable.h"
#include "{{name}}.h"
{{#includes}}
#include "{{this}}"
{{/includes}}
using namespace {{namespace}};
DEFINE_STREAMABLE("{{javaName}}", {{name}});
void {{name}}::readObject (ObjectInputStream& {{inStreamArg}})
{
{{#superclassStreamable}}
{{super}}::readObject(in);
{{/superclassStreamable}}
{{#fields}}
{{name}} = {{reader}};
{{/fields}}
}
void {{name}}::writeObject (ObjectOutputStream& {{outStreamArg}}) const
{
{{#superclassStreamable}}
{{super}}::writeObject(out);
{{/superclassStreamable}}
{{#fields}}
{{writer}};
{{/fields}}
}
@@ -0,0 +1,26 @@
#pragma once
#include "presents/Streamable.h"
#include "presents/ObjectInputStream.h"
#include "presents/ObjectOutputStream.h"
#include "presents/streamers/StreamableStreamer.h"
{{#includes}}
#include "{{this}}"
{{/includes}}
{{#namespaces}}namespace {{this}} { {{/namespaces}}
class {{name}} : public {{super}} {
public:
DECLARE_STREAMABLE();
{{#fields}}
{{type.type}} {{name}};
{{/fields}}
virtual void readObject(ObjectInputStream& in);
virtual void writeObject(ObjectOutputStream& out) const;
};
{{#namespaces}}}{{/namespaces}}
@@ -0,0 +1,55 @@
package {{package}};
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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,53 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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 const RECEIVER_CODE :String = "{{receiver_code}}";
{{#methods}}
/** The method id used to dispatch {@link {{name}}Receiver#{{method.name}}}
* notifications. */
public static const {{code}} :int = {{-index}};
{{/methods}}
/**
* Creates a decoder that may be registered to dispatch invocation
* service notifications to the specified receiver.
*/
public function {{name}}Decoder (receiver :{{name}}Receiver)
{
this.receiver = receiver;
}
public override function getReceiverCode () :String
{
return RECEIVER_CODE;
}
public override function dispatchNotification (methodId :int, args :Array) :void
{
switch (methodId) {
{{#methods}}
case {{code}}:
{{name}}Receiver(receiver).{{method.name}}(
{{getASUnwrappedArgList}}
);
return;
{{/methods}}
default:
super.dispatchNotification(methodId, args);
}
}
}
}
@@ -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{{#hasArgs}}, {{/hasArgs}}{{getUnwrappedArgList}}
);
return;
{{/methods}}
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,36 @@
/**
* 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}}, {{field}}, 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}}, {{field}}, 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,49 @@
{{header}}// GENERATED PREAMBLE START
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
// GENERATED PREAMBLE END
// GENERATED CLASSDECL START
public final class {{classname}} extends Enum
{
// GENERATED CLASSDECL END
// GENERATED ENUM START
{{#enumFields}}
public static const {{name}} :{{classname}} = new {{classname}}("{{name}}");
{{/enumFields}}
finishedEnumerating({{classname}});
/**
* Gets the values of the {{classname}} enum.
*/
public static function values () :Array
{
return Enum.values({{classname}});
}
/**
* Gets the {{classname}} instance that corresponds to the specified string.
* If no such value exists, an ArgumentError will be thrown.
*/
public static function valueOf (name :String) :{{classname}}
{
return Enum.valueOf({{classname}}, name) as {{classname}};
}
/** @private */
public function {{classname}} (name :String)
{
super(name);
}
// GENERATED ENUM END
// GENERATED CLASSFINISH START
}
}
// GENERATED CLASSFINISH END
@@ -0,0 +1,79 @@
package {{package}};
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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{{typeParameters}}
implements {{name}}Service
{
{{#listeners}}
/**
* Marshalls results to implementations of {@code {{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}})
{
sendResponse({{code}}, new Object[] { {{getWrappedArgList}} });
}
{{/methods}}
@Override // from InvocationMarshaller
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 {{typeParams}}void {{method.name}} ({{getArgList}})
{
{{#listenerArgs}}
{{marshaller}} listener{{index}} = new {{marshaller}}();
listener{{index}}.listener = arg{{index}};
{{/listenerArgs}}
sendRequest({{code}}, new Object[] {
{{#hasArgs}}
{{getWrappedArgList}}
{{/hasArgs}}
}{{transport}});
}
{{/methods}}
}
@@ -0,0 +1,41 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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}} ({{getASArgList}}) :void
{
{{#listenerArgs}}
var listener{{index}} :{{actionScriptMarshaller}} = new {{actionScriptMarshaller}}();
listener{{index}}.listener = arg{{index}};
{{/listenerArgs}}
sendRequest({{code}}, [
{{#hasArgs}}
{{getASWrappedArgList}}
{{/hasArgs}}
]);
}
{{/methods}}
}
}
@@ -0,0 +1,37 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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,25 @@
package {{package}};
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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.
*/
{{typeParams}}void {{method.name}} ({{callerType}} caller{{#hasArgs}}, {{/hasArgs}}{{getArgList}}){{^listenerArgs.isEmpty}}
throws InvocationException{{/listenerArgs.isEmpty}};
{{/methods}}
}
@@ -0,0 +1,19 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
public interface {{name}}Receiver extends InvocationReceiver
{
{{#methods}}
{{^-first}}
{{/-first}}
// from Java interface {{name}}Receiver
function {{method.name}} ({{getASArgList}}) :void;
{{/methods}}
}
}
@@ -0,0 +1,29 @@
package {{package}};
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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,22 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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}} ({{getASArgList}}) :void;
{{/methods}}
}
}
@@ -0,0 +1,51 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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 (
{{listener.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,23 @@
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
/**
* 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,242 @@
{{header}}// GENERATED PREAMBLE START
package {{package}} {
{{#importGroups}}
{{#this}}
import {{this}};
{{/this}}
{{/importGroups}}
// 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
{{#pubFields}}
public var {{name}} :{{simpleType}};{{#hasTypeParameters}} /* of */ {{parameterTypes}};{{/hasTypeParameters}}
{{/pubFields}}
{{#dobject}}
{{#pubFields}}
public var {{name}}Changed :Signal = new Signal({{simpleType}}, {{simpleType}});
{{#dset}}
public var {{name}}EntryAdded :Signal = new Signal({{parameterTypes}});
public var {{name}}EntryRemoved :Signal = new Signal({{parameterTypes}});
public var {{name}}EntryUpdated :Signal = new Signal({{parameterTypes}}, {{parameterTypes}});
{{/dset}}
{{#array}}
public var {{name}}ElementUpdated :Signal = new Signal(int, Object, Object);
{{/array}}
{{#oidList}}
public var {{name}}ObjectAdded :Signal = new Signal(int);
public var {{name}}ObjectRemoved :Signal = new Signal(int);
{{/oidList}}
{{/pubFields}}
{{#pubFields}}
public static const {{dobjectField}} :String = "{{name}}";
{{/pubFields}}
{{/dobject}}
{{#superclassStreamable}}override {{/superclassStreamable}}public function readObject (ins :ObjectInputStream) :void
{
{{#superclassStreamable}}
super.readObject(ins);
{{/superclassStreamable}}
{{#pubFields}}
{{name}} = ins.{{reader}};
{{/pubFields}}
{{#protFields}}
{{name}} = ins.{{reader}};
{{/protFields}}
}
{{^dobject}}
{{#superclassStreamable}}override {{/superclassStreamable}}public function writeObject (out :ObjectOutputStream) :void
{
{{#superclassStreamable}}
super.writeObject(out);
{{/superclassStreamable}}
{{#pubFields}}
out.{{writer}};
{{/pubFields}}
{{#protFields}}
out.{{writer}};
{{/protFields}}
}
{{/dobject}}
{{#dobject}}
public function {{classname}} ()
{
new Signaller(this);
}
{{/dobject}}
{{#protFields}}
protected var {{name}} :{{simpleType}};
{{/protFields}}
// GENERATED STREAMING END
// GENERATED CLASSFINISH START
}
}
// GENERATED CLASSFINISH END
{{#dobject}}
// GENERATED SIGNALLER START
import org.osflash.signals.Signal;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.ElementUpdateListener;
import com.threerings.presents.dobj.ElementUpdatedEvent;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.presents.dobj.MessageListener;
import com.threerings.presents.dobj.ObjectAddedEvent;
import com.threerings.presents.dobj.ObjectDeathListener;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.ObjectRemovedEvent;
import com.threerings.presents.dobj.OidListListener;
import com.threerings.presents.dobj.SetListener;
import {{package}}.{{classname}};
class Signaller
implements AttributeChangeListener, SetListener, ElementUpdateListener, OidListListener
{
public function Signaller (obj :{{classname}})
{
_obj = obj;
_obj.addListener(this);
}
public function attributeChanged (event :AttributeChangedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
case "{{name}}":
signal = _obj.{{name}}Changed;
break;
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getValue(), event.getOldValue());
}
public function entryAdded (event :EntryAddedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
{{#dset}}
case "{{name}}":
signal = _obj.{{name}}EntryAdded;
break;
{{/dset}}
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getEntry());
}
public function entryRemoved (event :EntryRemovedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
{{#dset}}
case "{{name}}":
signal = _obj.{{name}}EntryRemoved;
break;
{{/dset}}
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getOldEntry());
}
public function entryUpdated (event :EntryUpdatedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
{{#dset}}
case "{{name}}":
signal = _obj.{{name}}EntryUpdated;
break;
{{/dset}}
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getEntry(), event.getOldEntry());
}
public function elementUpdated (event :ElementUpdatedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
{{#array}}
case "{{name}}":
signal = _obj.{{name}}ElementUpdated;
break;
{{/array}}
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getIndex(), event.getValue(), event.getOldValue());
}
public function objectAdded (event:ObjectAddedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
{{#oidList}}
case "{{name}}":
signal = _obj.{{name}}ObjectAdded;
break;
{{/oidList}}
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getOid());
}
public function objectRemoved (event :ObjectRemovedEvent) :void
{
var signal :Signal;
switch (event.getName()) {
{{#pubFields}}
{{#oidList}}
case "{{name}}":
signal = _obj.{{name}}ObjectRemoved;
break;
{{/oidList}}
{{/pubFields}}
default:
return;
}
signal.dispatch(event.getOid());
}
protected var _obj :{{classname}};
}
// GENERATED SIGNALLER END
{{/dobject}}
@@ -0,0 +1,82 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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 static org.junit.Assert.assertEquals;
import org.junit.Test;
public class GeneratedSourceMergerTest
{
@Test
public void mergeNothing ()
throws Exception
{
new GeneratedSourceMerger().merge("", "");
}
@Test
public void mergeUpdatedSection ()
throws Exception
{
String modified = before + "// GENERATED VARIABLE START\n" +
"var s :String = 'byte';\n" +
"// GENERATED VARIABLE END\n" + after;
assertEquals(generated, new GeneratedSourceMerger().merge(generated, modified));
}
@Test
public void mergeMissingSection ()
throws Exception
{
assertEquals(section + before, new GeneratedSourceMerger().merge(generated, before));
}
@Test
public void ignoreDisabledSection ()
throws Exception
{
String disabled = "// GENERATED VARIABLE DISABLED\n" + before;
assertEquals(disabled, new GeneratedSourceMerger().merge(generated, disabled));
}
@Test
public void dropOldSection ()
throws Exception
{
String previous = "// GENERATED PREVIOUS START\n" +
"var noLongerNeeded :String = 'hi';\n" +
"// GENERATED PREVIOUS END\n" +
generated;
assertEquals(generated, new GeneratedSourceMerger().merge(generated, previous));
}
String section = "// GENERATED VARIABLE START\n" +
"var s :String = 'hi';\n" +
"// GENERATED VARIABLE END\n";
String before = "var r :int = 7;\n";
String after = "var t :Array = [];\n";
String generated = before + section + after;
}