Add a generated source updating tool and an actionscript streamable generator built with it.
GeneratedSourceMerger merges the latest version of generated source into a previously generated version a la GenRecordTask SourceFile. Unlike them, it doesn't have a hardcoded set of sections to update. Instead it finds any sections using the // GENERATED <name> START and // GENERATED <name> END markers in newly generated version and replaces those same sections in the old version. GenActionScriptStreamableTask creates actionscript Streamable classes from their Java counterparts like GenActionScriptTask, but unlike GenActionScriptTask it doesn't attempt to parse Java source to generate actionscript. It only generates actionscript code to do streaming and leaves any custom implementation to the user. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6196 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.presents.tools;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.File;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
public class GenActionScriptStreamableTask extends GenActionScriptTask
|
||||
{
|
||||
@Override
|
||||
protected void convert (File javaSource, Class<?> sclass, File outputLocation)
|
||||
throws Exception
|
||||
{
|
||||
// Generate the current version of the streamable
|
||||
StreamableClassRequirements reqs = new StreamableClassRequirements(sclass);
|
||||
|
||||
Set<String> imports = Sets.newLinkedHashSet();
|
||||
imports.add(ObjectInputStream.class.getName());
|
||||
imports.add(ObjectOutputStream.class.getName());
|
||||
String extendsName = "";
|
||||
if (!sclass.getSuperclass().equals(Object.class)) {
|
||||
extendsName = addImportAndGetShortType(sclass.getSuperclass(), false, imports);
|
||||
}
|
||||
|
||||
Set<String> implemented = Sets.newLinkedHashSet();
|
||||
for (Class<?> iface : sclass.getInterfaces()) {
|
||||
implemented.add(addImportAndGetShortType(iface, false, imports));
|
||||
}
|
||||
List<ASField> fields = Lists.newArrayList();
|
||||
for (Field f : reqs.streamedFields) {
|
||||
fields.add(new ASField(f, imports));
|
||||
}
|
||||
String output = mergeTemplate("com/threerings/presents/tools/streamable_as.tmpl",
|
||||
"header", _header,
|
||||
"package", sclass.getPackage().getName(),
|
||||
"classname", sclass.getSimpleName(),
|
||||
"imports", imports,
|
||||
"extends", extendsName,
|
||||
"implements", Joiner.on(", ").join(implemented),
|
||||
"superclassStreamable", reqs.superclassStreamable,
|
||||
"fields", fields);
|
||||
|
||||
if (outputLocation.exists()) {
|
||||
// Merge in the previously generated version
|
||||
String existing = Files.toString(outputLocation, Charsets.UTF_8);
|
||||
output = new GeneratedSourceMerger().merge(output, existing);
|
||||
} else if (!outputLocation.getParentFile().exists()) {
|
||||
// Make sure the directory exists before trying to write there
|
||||
Preconditions.checkArgument(outputLocation.getParentFile().mkdirs(),
|
||||
"Unable to create directory to write '%s'", outputLocation.getAbsolutePath());
|
||||
}
|
||||
Files.write(output, outputLocation, Charsets.UTF_8);
|
||||
}
|
||||
|
||||
protected static class ASField
|
||||
{
|
||||
public final String name;
|
||||
public final String simpleType;
|
||||
public final String reader;
|
||||
public final String writer;
|
||||
|
||||
public ASField (Field f, Set<String> imports)
|
||||
{
|
||||
this.name = f.getName();
|
||||
this.simpleType = addImportAndGetShortType(f.getType(), true, imports);
|
||||
this.reader = toReadObject(f.getType());
|
||||
this.writer = toWriteObject(f.getType(), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,21 +25,23 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
@@ -54,7 +56,7 @@ import com.threerings.presents.dobj.DObject;
|
||||
* Generates ActionScript versions of {@link Streamable} classes and provides routines used by the
|
||||
* {@link GenDObjectTask} to create ActionScript versions of distributed objects.
|
||||
*/
|
||||
public class GenActionScriptTask extends Task
|
||||
public class GenActionScriptTask extends GenTask
|
||||
{
|
||||
/**
|
||||
* Adds a nested <fileset> element which enumerates streamable source files.
|
||||
@@ -78,7 +80,7 @@ public class GenActionScriptTask extends Task
|
||||
public void setHeader (File header)
|
||||
{
|
||||
try {
|
||||
_header = StreamUtil.toString(new FileReader(header));
|
||||
_header = Files.toString(header, Charsets.UTF_8);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Unabled to load header '" + header + ": " + ioe.getMessage());
|
||||
}
|
||||
@@ -116,17 +118,11 @@ public class GenActionScriptTask extends Task
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> sclass = loadClass(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.");
|
||||
processClass(source, sclass);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
throw new BuildException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +130,7 @@ public class GenActionScriptTask extends Task
|
||||
* Processes a resolved Streamable class instance.
|
||||
*/
|
||||
protected void processClass (File source, Class<?> sclass)
|
||||
throws IOException
|
||||
throws Exception
|
||||
{
|
||||
// make sure we implement Streamable but don't extend DObject or InvocationMarshaller and
|
||||
// that we're a class not an interface
|
||||
@@ -158,19 +154,20 @@ public class GenActionScriptTask extends Task
|
||||
} while (cclass != null);
|
||||
|
||||
// determine the path to the corresponding action script source file
|
||||
String path = sclass.getPackage().getName();
|
||||
path = path.replace(".", File.separator);
|
||||
String name = sclass.getName();
|
||||
name = name.substring(name.lastIndexOf(".")+1);
|
||||
path = path + File.separator + name;
|
||||
String path = toActionScriptType(sclass, false).replace(".", File.separator);
|
||||
File asfile = new File(_asroot, path + ".as");
|
||||
|
||||
System.err.println("Converting " + sclass.getName() + "...");
|
||||
convert(source, sclass, asfile);
|
||||
}
|
||||
|
||||
protected void convert (File javaSource, Class<?> sclass, File output)
|
||||
throws Exception
|
||||
{
|
||||
// parse the existing ActionScript source and generate what we don't
|
||||
// have from the Java class
|
||||
ActionScriptSource assrc = new ActionScriptSource(sclass);
|
||||
assrc.absorbJava(source);
|
||||
assrc.absorbJava(javaSource);
|
||||
assrc.imports.add(ObjectInputStream.class.getName());
|
||||
assrc.imports.add(ObjectOutputStream.class.getName());
|
||||
|
||||
@@ -193,7 +190,7 @@ public class GenActionScriptTask extends Task
|
||||
continue;
|
||||
}
|
||||
body.append(" ");
|
||||
body.append(field.getName()).append(" = ");
|
||||
body.append(field.getName()).append(" = ins.");
|
||||
body.append(toReadObject(field.getType()));
|
||||
body.append(";\n");
|
||||
added++;
|
||||
@@ -227,13 +224,13 @@ public class GenActionScriptTask extends Task
|
||||
}
|
||||
|
||||
// now we can parse existing definitions from any extant ActionScript source file
|
||||
assrc.absorbActionScript(asfile);
|
||||
assrc.absorbActionScript(output);
|
||||
|
||||
// make sure our parent directory exists
|
||||
asfile.getParentFile().mkdirs();
|
||||
output.getParentFile().mkdirs();
|
||||
|
||||
// now write all that out to the target source file
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(asfile));
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(output));
|
||||
assrc.write(new PrintWriter(out));
|
||||
}
|
||||
|
||||
@@ -243,65 +240,123 @@ public class GenActionScriptTask extends Task
|
||||
return !Modifier.isStatic(mods) && !Modifier.isTransient(mods);
|
||||
}
|
||||
|
||||
protected String toReadObject (Class<?> type)
|
||||
protected static String addImportAndGetShortType (Class<?> type, boolean isField,
|
||||
Set<String> imports)
|
||||
{
|
||||
String full = toActionScriptType(type, isField);
|
||||
if (needsActionScriptImport(type, isField)) {
|
||||
imports.add(full);
|
||||
}
|
||||
return getSimpleType(full);
|
||||
}
|
||||
|
||||
protected static boolean needsActionScriptImport (Class<?> type, boolean isField)
|
||||
{
|
||||
if (type.isArray()) {
|
||||
return Byte.TYPE.equals(type.getComponentType()) || isField;
|
||||
}
|
||||
return (Long.TYPE.equals(type) || !type.isPrimitive()) && !String.class.equals(type);
|
||||
}
|
||||
|
||||
protected static String toActionScriptType (Class<?> type, boolean isField)
|
||||
{
|
||||
if (type.isArray()) {
|
||||
if (Byte.TYPE.equals(type.getComponentType())) {
|
||||
return "flash.utils.ByteArray";
|
||||
}
|
||||
if (isField) {
|
||||
return "com.threerings.io.TypedArray";
|
||||
}
|
||||
return "Array";
|
||||
}
|
||||
|
||||
if (Integer.TYPE.equals(type) ||
|
||||
Byte.TYPE.equals(type) ||
|
||||
Short.TYPE.equals(type) ||
|
||||
Character.TYPE.equals(type)) {
|
||||
return "int";
|
||||
}
|
||||
|
||||
if (Float.TYPE.equals(type) ||
|
||||
Double.TYPE.equals(type)) {
|
||||
return "Number";
|
||||
}
|
||||
|
||||
if (Long.TYPE.equals(type)) {
|
||||
return "com.threerings.util.Long";
|
||||
}
|
||||
|
||||
if (Boolean.TYPE.equals(type)) {
|
||||
return "Boolean";
|
||||
}
|
||||
|
||||
return type.getName().replaceAll("\\$", "_");
|
||||
}
|
||||
|
||||
public static String toReadObject (Class<?> type)
|
||||
{
|
||||
if (type.equals(String.class)) {
|
||||
return "ins.readField(String)";
|
||||
return "readField(String)";
|
||||
|
||||
} else if (type.equals(Integer.class) ||
|
||||
type.equals(Short.class) ||
|
||||
type.equals(Byte.class)) {
|
||||
String name = ActionScriptSource.toSimpleName(type.getName());
|
||||
return "ins.readField(" + name + ").value";
|
||||
return "readField(" + name + ").value";
|
||||
|
||||
} else if (type.equals(Long.class)) {
|
||||
String name = ActionScriptSource.toSimpleName(type.getName());
|
||||
return "ins.readField(" + name + ")";
|
||||
return "readField(" + name + ")";
|
||||
|
||||
} else if (type.equals(Boolean.TYPE)) {
|
||||
return "ins.readBoolean()";
|
||||
return "readBoolean()";
|
||||
|
||||
} else if (type.equals(Byte.TYPE)) {
|
||||
return "ins.readByte()";
|
||||
return "readByte()";
|
||||
|
||||
} else if (type.equals(Short.TYPE)) {
|
||||
return "ins.readShort()";
|
||||
} else if (type.equals(Short.TYPE) || type.equals(Character.TYPE)) {
|
||||
return "readShort()";
|
||||
|
||||
} else if (type.equals(Integer.TYPE)) {
|
||||
return "ins.readInt()";
|
||||
return "readInt()";
|
||||
|
||||
} else if (type.equals(Long.TYPE)) {
|
||||
return "new Long(ins.readInt(), ins.readInt())";
|
||||
return "readLong()";
|
||||
|
||||
} else if (type.equals(Float.TYPE)) {
|
||||
return "ins.readFloat()";
|
||||
return "readFloat()";
|
||||
|
||||
} else if (type.equals(Double.TYPE)) {
|
||||
return "ins.readDouble()";
|
||||
return "readDouble()";
|
||||
|
||||
} else if (type.isArray()) {
|
||||
if (!type.getComponentType().isPrimitive()) {
|
||||
return "ins.readObject(TypedArray)";
|
||||
return "readObject(TypedArray)";
|
||||
} else {
|
||||
if (Double.TYPE.equals(type.getComponentType())) {
|
||||
return "ins.readField(TypedArray.getJavaType(Number))";
|
||||
return "readField(TypedArray.getJavaType(Number))";
|
||||
} else if (Boolean.TYPE.equals(type.getComponentType())) {
|
||||
return "ins.readField(TypedArray.getJavaType(Boolean))";
|
||||
return "readField(TypedArray.getJavaType(Boolean))";
|
||||
} else if (Integer.TYPE.equals(type.getComponentType())) {
|
||||
return "ins.readField(TypedArray.getJavaType(int))";
|
||||
return "readField(TypedArray.getJavaType(int))";
|
||||
} else if (Byte.TYPE.equals(type.getComponentType())) {
|
||||
return "ins.readField(ByteArray)";
|
||||
return "readField(ByteArray)";
|
||||
} else {
|
||||
throw new IllegalArgumentException(type
|
||||
+ " isn't supported to stream to actionscript");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "ins.readObject(" + ActionScriptSource.toSimpleName(type.getName()) + ")";
|
||||
return "readObject(" + ActionScriptSource.toSimpleName(type.getName()) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
protected String toWriteObject (Class<?> type, String name)
|
||||
public static String getSimpleType(String fullType)
|
||||
{
|
||||
return Iterables.getLast(DOT_SPLITTER.split(fullType));
|
||||
}
|
||||
|
||||
public static String toWriteObject (Class<?> type, String name)
|
||||
{
|
||||
if (type.equals(Integer.class)) {
|
||||
return "writeObject(new Integer(" + name + "))";
|
||||
@@ -315,16 +370,14 @@ public class GenActionScriptTask extends Task
|
||||
} else if (type.equals(Byte.TYPE)) {
|
||||
return "writeByte(" + name + ")";
|
||||
|
||||
} else if (type.equals(Short.TYPE)) {
|
||||
} 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 "writeInt(" + name + " == null ? 0 : " + name + ".low);\n" +
|
||||
" out.writeInt(" +
|
||||
name + " == null ? 0 : " + name + ".high)";
|
||||
return "writeLong(" + name + ")";
|
||||
|
||||
} else if (type.equals(Float.TYPE)) {
|
||||
return "writeFloat(" + name + ")";
|
||||
@@ -354,4 +407,6 @@ public class GenActionScriptTask extends Task
|
||||
"public function readObject (ins :ObjectInputStream) :void";
|
||||
protected static final String WRITE_SIG =
|
||||
"public function writeObject (out :ObjectOutputStream) :void";
|
||||
|
||||
protected static final Splitter DOT_SPLITTER = Splitter.on('.');
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
package com.threerings.presents.tools;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.io.File;
|
||||
@@ -35,7 +33,6 @@ import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
@@ -104,9 +101,11 @@ public class GenStreamableTask extends Task
|
||||
protected void processClass (File source, Class<?> sclass)
|
||||
throws IOException
|
||||
{
|
||||
// we must implement Streamable, not be a DObject and not be an interface ourselves
|
||||
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) ||
|
||||
Modifier.isInterface(sclass.getModifiers())) {
|
||||
reqs.streamedFields.isEmpty()) {
|
||||
// System.err.println("Skipping " + sclass.getName() + "...");
|
||||
return;
|
||||
}
|
||||
@@ -115,22 +114,11 @@ public class GenStreamableTask extends Task
|
||||
StringBuilder readbuf = new StringBuilder(READ_OPEN);
|
||||
StringBuilder writebuf = new StringBuilder(WRITE_OPEN);
|
||||
|
||||
// see if our parent also implements Streamable
|
||||
Class<?> supster = sclass.getSuperclass();
|
||||
do {
|
||||
if (isStreamableClass(supster)) {
|
||||
readbuf.append(" super.readObject(ins);\n");
|
||||
writebuf.append(" super.writeObject(out);\n");
|
||||
break;
|
||||
}
|
||||
supster = supster.getSuperclass();
|
||||
} while (supster != null);
|
||||
|
||||
int added = 0;
|
||||
for (Field field : sclass.getDeclaredFields()) {
|
||||
if (!isStreamableField(field)) {
|
||||
continue;
|
||||
}
|
||||
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));
|
||||
@@ -139,10 +127,6 @@ public class GenStreamableTask extends Task
|
||||
writebuf.append(" out.");
|
||||
writebuf.append(toWriteObject(field));
|
||||
writebuf.append(";\n");
|
||||
added++;
|
||||
}
|
||||
if (added == 0) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
readbuf.append(READ_CLOSE);
|
||||
@@ -179,22 +163,6 @@ public class GenStreamableTask extends Task
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isStreamableClass (Class<?> clazz)
|
||||
{
|
||||
for (Class<?> iface : clazz.getInterfaces()) {
|
||||
if (Streamable.class.equals(iface)) {
|
||||
return !SimpleStreamableObject.class.equals(clazz);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isStreamableField (Field field)
|
||||
{
|
||||
int mods = field.getModifiers();
|
||||
return !Modifier.isStatic(mods) && !Modifier.isTransient(mods);
|
||||
}
|
||||
|
||||
protected String toReadObject (Field field)
|
||||
{
|
||||
Class<?> type = field.getType();
|
||||
|
||||
@@ -21,16 +21,15 @@
|
||||
|
||||
package com.threerings.presents.tools;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.apache.tools.ant.AntClassLoader;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.Reference;
|
||||
import org.apache.tools.ant.util.ClasspathUtils;
|
||||
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.presents.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
/**
|
||||
* Merges updates to a generated source file into a previously generated version of that source
|
||||
* while leaving changes outside marked sections alone.
|
||||
*/
|
||||
public class GeneratedSourceMerger
|
||||
{
|
||||
/**
|
||||
* Returns <code>previouslyGenerated</code> with marked sections updated from the same marked
|
||||
* sections in <code>newlyGenerated</code> Everything outside these sections in
|
||||
* <code>previouslyGenerated</code> is returned as is. A marked section starts with
|
||||
* "// GENERATED <name> START" and ends with "// GENERATED <name> END"
|
||||
*/
|
||||
public String merge (String newlyGenerated, String previouslyGenerated)
|
||||
throws Exception
|
||||
{
|
||||
// Extract the generated section names from the output and make sure they're all matched
|
||||
Map<String, String> sections = Maps.newLinkedHashMap();
|
||||
Matcher m = _sectionDelimiter.matcher(newlyGenerated);
|
||||
while (m.find()) {
|
||||
Tuple<String, String> section = extractGeneratedSection(m, newlyGenerated);
|
||||
Preconditions.checkArgument(!sections.containsKey(section.left),
|
||||
"Section '%s' used more than once", section.left);
|
||||
sections.put(section.left, section.right);
|
||||
}
|
||||
|
||||
// Merge with the previously generated source
|
||||
StringBuilder merged = new StringBuilder();
|
||||
m = _sectionDelimiter.matcher(previouslyGenerated);
|
||||
Iterator<Entry<String, String>> secs = sections.entrySet().iterator();
|
||||
int currentStart = 0;
|
||||
while (m.find()) {
|
||||
merged.append(previouslyGenerated.substring(currentStart, m.start()));
|
||||
Tuple<String, String> existingSection =
|
||||
extractGeneratedSection(m, previouslyGenerated);
|
||||
// Allow generated sections to be dropped in the template, but warn in case something
|
||||
// odd's happening
|
||||
if (!secs.hasNext()) {
|
||||
System.err.println("Dropping previously generated section '" + m.group(1)
|
||||
+ "' that's no longer generated by the template");
|
||||
}
|
||||
// However, we don't allow sections to be removed or reordered in the generated code
|
||||
Entry<String, String> newSection = secs.next();
|
||||
Preconditions.checkArgument(newSection.getKey().equals(existingSection.left),
|
||||
"Generated code expected '%s' section, but encountered '%s'", newSection.getKey(),
|
||||
existingSection.left);
|
||||
// Pop in the newly generated code in the place of what was there before
|
||||
merged.append(newSection.getValue());
|
||||
currentStart = m.end();
|
||||
}
|
||||
// Add generated sections that weren't present in the old output before the last
|
||||
// non-generated code. It's a 50-50 shot, so warn when this happens
|
||||
while(secs.hasNext()) {
|
||||
Entry<String, String> newSection = secs.next();
|
||||
System.err.println("Adding previously missing generated section '"
|
||||
+ newSection.getKey() + "' before the last non-generated text");
|
||||
merged.append(newSection.getValue());
|
||||
}
|
||||
// Add any text past the last previously generated section
|
||||
merged.append(previouslyGenerated.substring(currentStart));
|
||||
|
||||
return merged.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a section name and its contents from the given matcher pointing to the start of a
|
||||
* section. <code>m</code> is at the end of the section when this returns.
|
||||
*/
|
||||
protected Tuple<String, String> extractGeneratedSection (Matcher m, String input)
|
||||
{
|
||||
int startIdx = m.start();
|
||||
String startName = m.group(1);
|
||||
Preconditions.checkArgument(m.group(2).equals("START"), "'%s' END without START",
|
||||
startName);
|
||||
Preconditions.checkArgument(m.find(), "'%s' START without END", startName);
|
||||
String endName = m.group(1);
|
||||
Preconditions.checkArgument(m.group(2).equals("END"),
|
||||
"'%s' START after '%s' START", endName, startName);
|
||||
Preconditions.checkArgument(endName.equals(startName),
|
||||
"'%s' END after '%s' START", endName, startName);
|
||||
return Tuple.newTuple(startName, input.substring(startIdx, m.end()));
|
||||
}
|
||||
|
||||
protected final Pattern _sectionDelimiter = Pattern.compile(" *// GENERATED (\\w+) (START|END)\n");
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.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,48 @@
|
||||
// GENERATED PREAMBLE START
|
||||
${header}
|
||||
package ${package} {
|
||||
|
||||
#foreach ($import in $imports)
|
||||
import $import;
|
||||
#end
|
||||
// GENERATED PREAMBLE END
|
||||
|
||||
// GENERATED CLASSDECL START
|
||||
public class ${classname} #if (!${extends.isEmpty()})extends $extends
|
||||
#end
|
||||
#if (!${implements.isEmpty()})
|
||||
implements $implements
|
||||
#end
|
||||
{
|
||||
public function ${classname} ()
|
||||
{}
|
||||
// GENERATED CLASSDECL END
|
||||
|
||||
// GENERATED STREAMING START
|
||||
#foreach ($field in $fields)
|
||||
public var $field.name :${field.simpleType};
|
||||
#end
|
||||
|
||||
#if ($superclassStreamable)override #{end}public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
#if ($superclassStreamable)
|
||||
super.readObject(ins);
|
||||
#end
|
||||
#foreach ($field in $fields)
|
||||
$field.name = ins.${field.reader};
|
||||
#end
|
||||
}
|
||||
|
||||
#if ($superclassStreamable)override #{end}public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
#if ($superclassStreamable)
|
||||
super.writeObject(out);
|
||||
#end
|
||||
#foreach ($field in $fields)
|
||||
out.${field.writer};
|
||||
#end
|
||||
}
|
||||
// GENERATED STREAMING END
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user