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:
@@ -24,6 +24,7 @@ package com.threerings.crowd.data;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.util.ActionScript;
|
||||
|
||||
import com.threerings.crowd.Log;
|
||||
import com.threerings.crowd.client.PlaceController;
|
||||
@@ -89,6 +90,7 @@ public abstract class PlaceConfig extends SimpleStreamableObject
|
||||
public abstract String getManagerClassName ();
|
||||
|
||||
// documentation inherited
|
||||
@ActionScript(name="toStringBuilder")
|
||||
protected void toString (StringBuilder buf)
|
||||
{
|
||||
buf.append("type=").append(StringUtil.shortClassName(this));
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
package com.threerings.io;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.threerings.util.ActionScript;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* classes can override and include non-public members if desired.
|
||||
*/
|
||||
@ActionScript(name="toStringBuilder")
|
||||
protected void toString (StringBuilder buf)
|
||||
{
|
||||
StringUtil.fieldsToString(buf, this);
|
||||
|
||||
@@ -37,6 +37,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.threerings.util.ActionScript;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Java field in ActionScript.
|
||||
*/
|
||||
public static String createActionScriptDeclaration (Field field)
|
||||
public static String createActionScriptDeclaration (
|
||||
String name, Field field)
|
||||
{
|
||||
int mods = field.getModifiers();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@@ -158,7 +160,7 @@ public class ActionScriptSource
|
||||
builder.append(Modifier.isFinal(mods) ? "const " : "var ");
|
||||
|
||||
// next comes the name
|
||||
builder.append(field.getName()).append(" :");
|
||||
builder.append(name).append(" :");
|
||||
|
||||
// now convert the type to an ActionScript type
|
||||
builder.append(toActionScriptType(field.getType(),
|
||||
@@ -207,7 +209,8 @@ public class ActionScriptSource
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String createActionScriptDeclaration (Method method)
|
||||
public static String createActionScriptDeclaration (
|
||||
String name, Method method)
|
||||
{
|
||||
int mods = method.getModifiers();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@@ -230,7 +233,7 @@ public class ActionScriptSource
|
||||
builder.append("function ");
|
||||
|
||||
// next comes the name
|
||||
builder.append(getName(method)).append(" (");
|
||||
builder.append(name).append(" (");
|
||||
|
||||
// now the parameters
|
||||
int idx = 0;
|
||||
@@ -271,23 +274,6 @@ public class ActionScriptSource
|
||||
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)
|
||||
{
|
||||
packageName = jclass.getPackage().getName();
|
||||
@@ -319,8 +305,18 @@ public class ActionScriptSource
|
||||
} else {
|
||||
list = Modifier.isPublic(mods) ? publicFields : protectedFields;
|
||||
}
|
||||
list.add(new Member(field.getName(),
|
||||
createActionScriptDeclaration(field)));
|
||||
String name = field.getName();
|
||||
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
|
||||
@@ -329,6 +325,10 @@ public class ActionScriptSource
|
||||
Constructor mainctor = null;
|
||||
boolean needsNoArg = false;
|
||||
for (Constructor ctor : jclass.getConstructors()) {
|
||||
ActionScript asa = ctor.getAnnotation(ActionScript.class);
|
||||
if (asa != null && asa.omit()) {
|
||||
continue;
|
||||
}
|
||||
int params = ctor.getParameterTypes().length;
|
||||
if (mainctor == null ||
|
||||
params > mainctor.getParameterTypes().length) {
|
||||
@@ -359,10 +359,20 @@ public class ActionScriptSource
|
||||
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
|
||||
String name = getName(method);
|
||||
String decl = createActionScriptDeclaration(method);
|
||||
Member mem = getMember(list, name);
|
||||
String decl = createActionScriptDeclaration(name, method);
|
||||
if (mem == null) {
|
||||
mem = new Member(name, decl);
|
||||
mem.body = " {\n TODO: IMPLEMENT ME\n }\n";
|
||||
@@ -944,11 +954,13 @@ public class ActionScriptSource
|
||||
|
||||
protected static Pattern ASFIELD = Pattern.compile(
|
||||
"\\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\\[\\]<>,]+)" + // type
|
||||
"(\\s+=.*|;)");
|
||||
|
||||
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.
|
||||
*/
|
||||
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 {
|
||||
_velocity = VelocityUtil.createEngine();
|
||||
} catch (Exception 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) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
@@ -155,7 +132,11 @@ public class GenActionScriptTask extends Task
|
||||
}
|
||||
|
||||
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) {
|
||||
System.err.println(
|
||||
"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
|
||||
// InvocationMarshaller and that we're a class not an interface
|
||||
if (!_sclass.isAssignableFrom(sclass) ||
|
||||
_doclass.isAssignableFrom(sclass) ||
|
||||
_imclass.isAssignableFrom(sclass) ||
|
||||
if (!Streamable.class.isAssignableFrom(sclass) ||
|
||||
DObject.class.isAssignableFrom(sclass) ||
|
||||
InvocationMarshaller.class.isAssignableFrom(sclass) ||
|
||||
((sclass.getModifiers() & Modifier.INTERFACE) != 0)) {
|
||||
// System.err.println("Skipping " + sclass.getName() + "...");
|
||||
return;
|
||||
@@ -273,7 +254,7 @@ public class GenActionScriptTask extends Task
|
||||
protected boolean isStreamable (Class clazz)
|
||||
{
|
||||
for (Class iface : clazz.getInterfaces()) {
|
||||
if (_sclass.equals(iface)) {
|
||||
if (Streamable.class.equals(iface)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -349,24 +330,9 @@ public class GenActionScriptTask extends Task
|
||||
/** The path to our ActionScript source files. */
|
||||
protected File _asroot;
|
||||
|
||||
/** Used to do our own classpath business. */
|
||||
protected ClassLoader _cloader;
|
||||
|
||||
/** Used to generate source files from templates. */
|
||||
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 =
|
||||
"public function readObject (ins :ObjectInputStream) :void";
|
||||
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 "";
|
||||
}
|
||||
Reference in New Issue
Block a user