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:
@@ -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 <fileset> 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 <fileset> 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 <fileset> element which enumerates streamable class files.
|
||||
*/
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a <path> element which defines our classpath.
|
||||
*/
|
||||
public void addPath (Path path)
|
||||
{
|
||||
_paths.add(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the directory into which we write our instrumented class files.
|
||||
*/
|
||||
public void setOutdir (File outdir)
|
||||
{
|
||||
_outdir = outdir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute ()
|
||||
{
|
||||
// configure our ClassPool with our classpath
|
||||
for (Path path : _paths) {
|
||||
for (String element : path.list()) {
|
||||
try {
|
||||
_pool.appendClassPath(element);
|
||||
} catch (NotFoundException nfe) {
|
||||
System.err.println("Invalid classpath entry [path=" + element + "]: " + nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// instantiate streamable
|
||||
try {
|
||||
_streamable = _pool.get(Streamable.class.getName());
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Unable to load " + Streamable.class.getName() + ": " + e);
|
||||
}
|
||||
|
||||
// now process the files
|
||||
for (FileSet fs : _filesets) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
for (String srcFile : srcFiles) {
|
||||
processClass(new File(fromDir, srcFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a class file.
|
||||
*/
|
||||
protected void processClass (File source)
|
||||
{
|
||||
CtClass clazz;
|
||||
InputStream in = null;
|
||||
try {
|
||||
clazz = _pool.makeClass(in = new BufferedInputStream(new FileInputStream(source)));
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Failed to load " + source + ": " + ioe);
|
||||
return;
|
||||
} finally {
|
||||
StreamUtil.close(in);
|
||||
}
|
||||
|
||||
try {
|
||||
if (clazz.subtypeOf(_streamable)) {
|
||||
processStreamable(source, clazz);
|
||||
}
|
||||
} catch (NotFoundException nfe) {
|
||||
System.err.println("Error processing class [class=" + clazz.getName() +
|
||||
", error=" + nfe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruments the supplied {@link Streamable} implementing class.
|
||||
*/
|
||||
protected void processStreamable (File source, CtClass clazz)
|
||||
throws NotFoundException
|
||||
{
|
||||
ArrayList<CtField> fields = Lists.newArrayList();
|
||||
for (CtField field : clazz.getDeclaredFields()) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers) ||
|
||||
!(Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers))) {
|
||||
continue;
|
||||
}
|
||||
fields.add(field);
|
||||
}
|
||||
|
||||
HashSet<String> methods = Sets.newHashSet();
|
||||
for (CtMethod method : clazz.getMethods()) {
|
||||
methods.add(method.getName());
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
for (CtField field : fields) {
|
||||
String rname = FieldMarshaller.getReaderMethodName(field.getName());
|
||||
if (!methods.contains(rname)) {
|
||||
String reader =
|
||||
"public void " + rname + " (com.threerings.io.ObjectInputStream ins) {\n" +
|
||||
// " throws java.io.IOException, java.lang.ClassNotFoundException\n" +
|
||||
" " + getFieldReader(field) + "\n" +
|
||||
"}";
|
||||
// System.out.println("Adding reader " + clazz.getName() + ":\n" + reader);
|
||||
try {
|
||||
clazz.addMethod(CtNewMethod.make(reader, clazz));
|
||||
added++;
|
||||
} catch (CannotCompileException cce) {
|
||||
System.err.println("Unable to compile reader [class=" + clazz.getName() +
|
||||
", error=" + cce + "]:");
|
||||
System.err.println(reader);
|
||||
}
|
||||
}
|
||||
|
||||
String wname = FieldMarshaller.getWriterMethodName(field.getName());
|
||||
if (!methods.contains(wname)) {
|
||||
String writer =
|
||||
"public void " + wname + " (com.threerings.io.ObjectOutputStream out) {\n" +
|
||||
// " throws java.io.IOException\n" +
|
||||
" " + getFieldWriter(field) + "\n" +
|
||||
"}";
|
||||
// System.out.println("Adding writer " + clazz.getName() + ":\n" + writer);
|
||||
try {
|
||||
clazz.addMethod(CtNewMethod.make(writer, clazz));
|
||||
added++;
|
||||
} catch (CannotCompileException cce) {
|
||||
System.err.println("Unable to compile writer [class=" + clazz.getName() +
|
||||
", error=" + cce + "]:");
|
||||
System.err.println(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (added > 0) {
|
||||
try {
|
||||
System.out.println("Instrumented '" + clazz.getName() + "'.");
|
||||
clazz.writeFile(_outdir.getPath());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to write instrumented class [class=" + clazz +
|
||||
", outdir=" + _outdir + "]: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String getFieldReader (CtField field)
|
||||
throws NotFoundException
|
||||
{
|
||||
CtClass type = field.getType();
|
||||
String name = field.getName();
|
||||
if (type.getName().equals("java.lang.String")) {
|
||||
return readWrap(field, name + " = ins.readUTF();");
|
||||
} else if (type.equals(CtClass.booleanType) || type.getName().equals("java.lang.Boolean")) {
|
||||
return readWrap(field, name + " = ins.readBoolean();");
|
||||
} else if (type.equals(CtClass.byteType) || type.getName().equals("java.lang.Byte")) {
|
||||
return readWrap(field, name + " = ins.readByte();");
|
||||
} else if (type.equals(CtClass.shortType) || type.getName().equals("java.lang.Short")) {
|
||||
return readWrap(field, name + " = ins.readShort();");
|
||||
} else if (type.equals(CtClass.intType) || type.getName().equals("java.lang.Integer")) {
|
||||
return readWrap(field, name + " = ins.readInt();");
|
||||
} else if (type.equals(CtClass.longType) || type.getName().equals("java.lang.Long")) {
|
||||
return readWrap(field, name + " = ins.readLong();");
|
||||
} else if (type.equals(CtClass.floatType) || type.getName().equals("java.lang.Float")) {
|
||||
return readWrap(field, name + " = ins.readFloat();");
|
||||
} else if (type.equals(CtClass.doubleType) || type.getName().equals("java.lang.Double")) {
|
||||
return readWrap(field, name + " = ins.readDouble();");
|
||||
}
|
||||
|
||||
if (type.isArray()) {
|
||||
CtClass ctype = type.getComponentType();
|
||||
if (ctype.equals(CtClass.booleanType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readBooleanArray(ins);");
|
||||
} else if (ctype.equals(CtClass.byteType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readByteArray(ins);");
|
||||
} else if (ctype.equals(CtClass.shortType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readShortArray(ins);");
|
||||
} else if (ctype.equals(CtClass.intType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readIntArray(ins);");
|
||||
} else if (ctype.equals(CtClass.longType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readLongArray(ins);");
|
||||
} else if (ctype.equals(CtClass.floatType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readFloat(ins);");
|
||||
} else if (ctype.equals(CtClass.doubleType)) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readDoubleArray(ins);");
|
||||
} else if (ctype.getName().equals("java.lang.Object")) {
|
||||
return readWrap(field, name + " = " + BSNAME + ".readObjectArray(ins);");
|
||||
}
|
||||
}
|
||||
|
||||
// no need to wrap streamable instances
|
||||
return (name + " = (" + type.getName() + ")ins.readObject();");
|
||||
}
|
||||
|
||||
protected String getFieldWriter (CtField field)
|
||||
throws NotFoundException
|
||||
{
|
||||
CtClass type = field.getType();
|
||||
String name = field.getName();
|
||||
|
||||
if (type.equals(CtClass.booleanType) || type.getName().equals("java.lang.Boolean")) {
|
||||
return writeWrap(field, "out.writeBoolean(" + name + ");");
|
||||
} else if (type.equals(CtClass.byteType) || type.getName().equals("java.lang.Byte")) {
|
||||
return writeWrap(field, "out.writeByte(" + name + ");");
|
||||
} else if (type.equals(CtClass.shortType) || type.getName().equals("java.lang.Short")) {
|
||||
return writeWrap(field, "out.writeShort(" + name + ");");
|
||||
} else if (type.equals(CtClass.intType) || type.getName().equals("java.lang.Integer")) {
|
||||
return writeWrap(field, "out.writeInt(" + name + ");");
|
||||
} else if (type.equals(CtClass.longType) || type.getName().equals("java.lang.Long")) {
|
||||
return writeWrap(field, "out.writeLong(" + name + ");");
|
||||
} else if (type.equals(CtClass.floatType) || type.getName().equals("java.lang.Float")) {
|
||||
return writeWrap(field, "out.writeFloat(" + name + ");");
|
||||
} else if (type.equals(CtClass.doubleType) || type.getName().equals("java.lang.Double")) {
|
||||
return writeWrap(field, "out.writeDouble(" + name + ");");
|
||||
} else if (type.getName().equals("java.lang.String")) {
|
||||
return writeWrap(field, "out.writeUTF(" + name + ");");
|
||||
}
|
||||
|
||||
if (type.isArray()) {
|
||||
CtClass ctype = type.getComponentType();
|
||||
if (ctype.equals(CtClass.booleanType)) {
|
||||
return writeWrap(field, BSNAME + ".writeBooleanArray(out, " + name + ");");
|
||||
} else if (ctype.equals(CtClass.byteType)) {
|
||||
return writeWrap(field, BSNAME + ".writeByteArray(out, " + name + ");");
|
||||
} else if (ctype.equals(CtClass.shortType)) {
|
||||
return writeWrap(field, BSNAME + ".writeShortArray(out, " + name + ");");
|
||||
} else if (ctype.equals(CtClass.intType)) {
|
||||
return writeWrap(field, BSNAME + ".writeIntArray(out, " + name + ");");
|
||||
} else if (ctype.equals(CtClass.longType)) {
|
||||
return writeWrap(field, BSNAME + ".writeLongArray(out, " + name + ");");
|
||||
} else if (ctype.equals(CtClass.floatType)) {
|
||||
return writeWrap(field, BSNAME + ".writeFloat(out, " + name + ");");
|
||||
} else if (ctype.equals(CtClass.doubleType)) {
|
||||
return writeWrap(field, BSNAME + ".writeDoubleArray(out, " + name + ");");
|
||||
} else if (ctype.getName().equals("java.lang.Object")) {
|
||||
return writeWrap(field, BSNAME + ".writeObjectArray(out, " + name + ");");
|
||||
}
|
||||
}
|
||||
|
||||
// no need to wrap streamable instances
|
||||
return "out.writeObject(" + name + ");";
|
||||
}
|
||||
|
||||
protected String readWrap (CtField field, String body)
|
||||
throws NotFoundException
|
||||
{
|
||||
if (field.getType().isPrimitive()) {
|
||||
return body;
|
||||
} else {
|
||||
return "if (ins.readBoolean()) {\n" +
|
||||
" " + body + "\n" +
|
||||
" } else {\n" +
|
||||
" " + field.getName() + " = null;\n" +
|
||||
" }";
|
||||
}
|
||||
}
|
||||
|
||||
protected String writeWrap (CtField field, String body)
|
||||
throws NotFoundException
|
||||
{
|
||||
if (field.getType().isPrimitive()) {
|
||||
return body;
|
||||
} else {
|
||||
return "if (" + field.getName() + " == null) {\n" +
|
||||
" out.writeBoolean(false);\n" +
|
||||
" } else {\n" +
|
||||
" out.writeBoolean(true);\n" +
|
||||
" " + body + "\n" +
|
||||
" }";
|
||||
}
|
||||
}
|
||||
|
||||
/** A list of filesets that contain Streamable class files. */
|
||||
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
|
||||
|
||||
/** A list of paths that make up our classpath. */
|
||||
protected ArrayList<Path> _paths = Lists.newArrayList();
|
||||
|
||||
/** The directory to which we write our instrumented class files. */
|
||||
protected File _outdir;
|
||||
|
||||
/** Used to instrument class files. */
|
||||
protected ClassPool _pool = ClassPool.getDefault();
|
||||
|
||||
/** Used to determine which classes implement {@link Streamable}. */
|
||||
protected CtClass _streamable;
|
||||
|
||||
protected static final String BSNAME = BasicStreamers.class.getName();
|
||||
}
|
||||
@@ -0,0 +1,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}}
|
||||
}
|
||||
}
|
||||
+51
@@ -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;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user