Now we have a cool @ActionScript annotation that allows us to specify different

field/method names for the ActionScript version of a class (or to omit
something entirely). This removes the need for special case hackery for
toStringBuilder().

In order for annotations to work, however, we have to require that the
GenActionScriptTask be loaded from the same classloader that loads the classes
to be reflected upon. Before we only reflected on the target classes, never
instantiated them. Annotations are actually instantiated, so we have to be able
to create an instance of the ActionScript.class that is compiled into our
target classes and assign it to a reference that is compiled into
GenActionScriptTask. Beware the complexities of dealing with multiple class
loaders.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4405 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2006-10-04 23:35:41 +00:00
parent ed51df0807
commit 4a9fde7830
5 changed files with 100 additions and 70 deletions
@@ -24,6 +24,7 @@ package com.threerings.crowd.data;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.io.SimpleStreamableObject; import com.threerings.io.SimpleStreamableObject;
import com.threerings.util.ActionScript;
import com.threerings.crowd.Log; import com.threerings.crowd.Log;
import com.threerings.crowd.client.PlaceController; import com.threerings.crowd.client.PlaceController;
@@ -89,6 +90,7 @@ public abstract class PlaceConfig extends SimpleStreamableObject
public abstract String getManagerClassName (); public abstract String getManagerClassName ();
// documentation inherited // documentation inherited
@ActionScript(name="toStringBuilder")
protected void toString (StringBuilder buf) protected void toString (StringBuilder buf)
{ {
buf.append("type=").append(StringUtil.shortClassName(this)); buf.append("type=").append(StringUtil.shortClassName(this));
@@ -22,6 +22,7 @@
package com.threerings.io; package com.threerings.io;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.util.ActionScript;
/** /**
* A simple serializable object implements the {@link Streamable} * A simple serializable object implements the {@link Streamable}
@@ -44,6 +45,7 @@ public class SimpleStreamableObject implements Streamable
* Handles the toString-ification of all public members. Derived * Handles the toString-ification of all public members. Derived
* classes can override and include non-public members if desired. * classes can override and include non-public members if desired.
*/ */
@ActionScript(name="toStringBuilder")
protected void toString (StringBuilder buf) protected void toString (StringBuilder buf)
{ {
StringUtil.fieldsToString(buf, this); StringUtil.fieldsToString(buf, this);
@@ -37,6 +37,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.util.ActionScript;
/** /**
* Primitively parses an ActionScriptSource file, allows the addition and * Primitively parses an ActionScriptSource file, allows the addition and
@@ -135,7 +136,8 @@ public class ActionScriptSource
* Creates and returns a declaration for a field equivalent to the supplied * Creates and returns a declaration for a field equivalent to the supplied
* Java field in ActionScript. * Java field in ActionScript.
*/ */
public static String createActionScriptDeclaration (Field field) public static String createActionScriptDeclaration (
String name, Field field)
{ {
int mods = field.getModifiers(); int mods = field.getModifiers();
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@@ -158,7 +160,7 @@ public class ActionScriptSource
builder.append(Modifier.isFinal(mods) ? "const " : "var "); builder.append(Modifier.isFinal(mods) ? "const " : "var ");
// next comes the name // next comes the name
builder.append(field.getName()).append(" :"); builder.append(name).append(" :");
// now convert the type to an ActionScript type // now convert the type to an ActionScript type
builder.append(toActionScriptType(field.getType(), builder.append(toActionScriptType(field.getType(),
@@ -207,7 +209,8 @@ public class ActionScriptSource
return builder.toString(); return builder.toString();
} }
public static String createActionScriptDeclaration (Method method) public static String createActionScriptDeclaration (
String name, Method method)
{ {
int mods = method.getModifiers(); int mods = method.getModifiers();
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@@ -230,7 +233,7 @@ public class ActionScriptSource
builder.append("function "); builder.append("function ");
// next comes the name // next comes the name
builder.append(getName(method)).append(" ("); builder.append(name).append(" (");
// now the parameters // now the parameters
int idx = 0; int idx = 0;
@@ -271,23 +274,6 @@ public class ActionScriptSource
return toSimpleName(tname); return toSimpleName(tname);
} }
protected static String getName (Method method)
{
// yay for the hackery; we can't overload methods in ActionScript so we
// do some crazy conversions based on signature to handle some of our
// common patterns
String name = method.getName();
Class<?>[] ptypes = method.getParameterTypes();
// toString(StringBuilder) -> toStringBuilder(StringBuilder)
if (name.equals("toString") && ptypes.length == 1 &&
ptypes[0].getName().equals("java.lang.StringBuilder")) {
return "toStringBuilder";
}
return name;
}
public ActionScriptSource (Class jclass) public ActionScriptSource (Class jclass)
{ {
packageName = jclass.getPackage().getName(); packageName = jclass.getPackage().getName();
@@ -319,8 +305,18 @@ public class ActionScriptSource
} else { } else {
list = Modifier.isPublic(mods) ? publicFields : protectedFields; list = Modifier.isPublic(mods) ? publicFields : protectedFields;
} }
list.add(new Member(field.getName(), String name = field.getName();
createActionScriptDeclaration(field))); ActionScript asa = field.getAnnotation(ActionScript.class);
if (asa != null) {
if (asa.omit()) {
continue;
}
if (!StringUtil.isBlank(asa.name())) {
name = asa.name();
}
}
String decl = createActionScriptDeclaration(name, field);
list.add(new Member(name, decl));
} }
// ActionScript only supports one constructor so we find the one with // ActionScript only supports one constructor so we find the one with
@@ -329,6 +325,10 @@ public class ActionScriptSource
Constructor mainctor = null; Constructor mainctor = null;
boolean needsNoArg = false; boolean needsNoArg = false;
for (Constructor ctor : jclass.getConstructors()) { for (Constructor ctor : jclass.getConstructors()) {
ActionScript asa = ctor.getAnnotation(ActionScript.class);
if (asa != null && asa.omit()) {
continue;
}
int params = ctor.getParameterTypes().length; int params = ctor.getParameterTypes().length;
if (mainctor == null || if (mainctor == null ||
params > mainctor.getParameterTypes().length) { params > mainctor.getParameterTypes().length) {
@@ -359,10 +359,20 @@ public class ActionScriptSource
publicMethods : protectedMethods; publicMethods : protectedMethods;
} }
String name = method.getName();
ActionScript asa = method.getAnnotation(ActionScript.class);
if (asa != null) {
if (asa.omit()) {
continue;
}
if (!StringUtil.isBlank(asa.name())) {
name = asa.name();
}
}
// see if we already have a member for this method name // see if we already have a member for this method name
String name = getName(method);
String decl = createActionScriptDeclaration(method);
Member mem = getMember(list, name); Member mem = getMember(list, name);
String decl = createActionScriptDeclaration(name, method);
if (mem == null) { if (mem == null) {
mem = new Member(name, decl); mem = new Member(name, decl);
mem.body = " {\n TODO: IMPLEMENT ME\n }\n"; mem.body = " {\n TODO: IMPLEMENT ME\n }\n";
@@ -944,11 +954,13 @@ public class ActionScriptSource
protected static Pattern ASFIELD = Pattern.compile( protected static Pattern ASFIELD = Pattern.compile(
"\\s+(?:public|protected|private)" + "\\s+(?:public|protected|private)" +
"(?:\\s+var|\\s+static\\s+const)?" + "(?:\\s+static)?(?:\\s+var|\\s+const)?" +
"\\s+([_a-zA-Z]\\w*)" + // variable name "\\s+([_a-zA-Z]\\w*)" + // variable name
"\\s+(?::[a-zA-Z\\[\\]<>,]+)" + // type "\\s+(?::[a-zA-Z\\[\\]<>,]+)" + // type
"(\\s+=.*|;)"); "(\\s+=.*|;)");
protected static Pattern ASFUNCTION = Pattern.compile( protected static Pattern ASFUNCTION = Pattern.compile(
".*(?:public|protected) function ([a-zA-Z]\\w*) \\(.*"); ".*(?:public|protected)" +
"(?:\\s+/\\*\\s*abstract\\s*\\*/)?" +
"\\s+function\\s+([a-zA-Z]\\w*) \\(.*");
} }
@@ -93,40 +93,17 @@ public class GenActionScriptTask extends Task
} }
} }
/**
* Configures the classpath that we'll use to load classes.
*/
public void setClasspathref (Reference pathref)
{
_cloader = ClasspathUtils.getClassLoaderForPath(getProject(), pathref);
}
/** /**
* Performs the actual work of the task. * Performs the actual work of the task.
*/ */
public void execute () throws BuildException public void execute () throws BuildException
{ {
if (_cloader == null) {
String errmsg = "This task requires a 'classpathref' attribute " +
"to be set to the project's classpath.";
throw new BuildException(errmsg);
}
try { try {
_velocity = VelocityUtil.createEngine(); _velocity = VelocityUtil.createEngine();
} catch (Exception e) { } catch (Exception e) {
throw new BuildException("Failure initializing Velocity", e); throw new BuildException("Failure initializing Velocity", e);
} }
// resolve the Streamable class using our classloader
try {
_sclass = _cloader.loadClass(Streamable.class.getName());
_doclass = _cloader.loadClass(DObject.class.getName());
_imclass = _cloader.loadClass(InvocationMarshaller.class.getName());
} catch (Exception e) {
throw new BuildException("Can't resolve Streamable", e);
}
for (FileSet fs : _filesets) { for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject()); DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject()); File fromDir = fs.getDir(getProject());
@@ -155,7 +132,11 @@ public class GenActionScriptTask extends Task
} }
try { try {
processClass(source, _cloader.loadClass(name)); // 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) { } catch (ClassNotFoundException cnfe) {
System.err.println( System.err.println(
"Failed to load " + name + ".\n" + "Failed to load " + name + ".\n" +
@@ -176,9 +157,9 @@ public class GenActionScriptTask extends Task
{ {
// make sure we implement Streamable but don't extend DObject or // make sure we implement Streamable but don't extend DObject or
// InvocationMarshaller and that we're a class not an interface // InvocationMarshaller and that we're a class not an interface
if (!_sclass.isAssignableFrom(sclass) || if (!Streamable.class.isAssignableFrom(sclass) ||
_doclass.isAssignableFrom(sclass) || DObject.class.isAssignableFrom(sclass) ||
_imclass.isAssignableFrom(sclass) || InvocationMarshaller.class.isAssignableFrom(sclass) ||
((sclass.getModifiers() & Modifier.INTERFACE) != 0)) { ((sclass.getModifiers() & Modifier.INTERFACE) != 0)) {
// System.err.println("Skipping " + sclass.getName() + "..."); // System.err.println("Skipping " + sclass.getName() + "...");
return; return;
@@ -273,7 +254,7 @@ public class GenActionScriptTask extends Task
protected boolean isStreamable (Class clazz) protected boolean isStreamable (Class clazz)
{ {
for (Class iface : clazz.getInterfaces()) { for (Class iface : clazz.getInterfaces()) {
if (_sclass.equals(iface)) { if (Streamable.class.equals(iface)) {
return true; return true;
} }
} }
@@ -349,24 +330,9 @@ public class GenActionScriptTask extends Task
/** The path to our ActionScript source files. */ /** The path to our ActionScript source files. */
protected File _asroot; protected File _asroot;
/** Used to do our own classpath business. */
protected ClassLoader _cloader;
/** Used to generate source files from templates. */ /** Used to generate source files from templates. */
protected VelocityEngine _velocity; protected VelocityEngine _velocity;
/** {@link Streamable} resolved with the proper classloader so that we can
* compare it to loaded derived classes. */
protected Class<?> _sclass;
/** {@link DObject} resolved with the proper classloader so that we
* can compare it to loaded derived classes. */
protected Class<?> _doclass;
/** {@link InvocationMarshaller} resolved with the proper classloader so
* that we can compare it to loaded derived classes. */
protected Class<?> _imclass;
protected static final String READ_SIG = protected static final String READ_SIG =
"public function readObject (ins :ObjectInputStream) :void"; "public function readObject (ins :ObjectInputStream) :void";
protected static final String WRITE_SIG = protected static final String WRITE_SIG =
@@ -0,0 +1,48 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/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.util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
/**
* An annotation that controls ActionScript code generation.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
public @interface ActionScript
{
/**
* Indicates whether this field, method or class should be omitted from the
* ActionScript translation.
*/
boolean omit () default false;
/**
* Indicates a custom name to be used for the ActionScript version of this
* field, method or class.
*/
String name () default "";
}