From 5d48f7726aeb983cce962a6624188f8ad432c2b4 Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Thu, 14 Oct 2010 22:55:44 +0000 Subject: [PATCH] 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 START and // GENERATED 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 --- .../tools/GenActionScriptStreamableTask.java | 102 ++++++++++++ .../presents/tools/GenActionScriptTask.java | 153 ++++++++++++------ .../presents/tools/GenStreamableTask.java | 50 ++---- .../threerings/presents/tools/GenTask.java | 5 +- .../presents/tools/GeneratedSourceMerger.java | 117 ++++++++++++++ .../tools/StreamableClassRequirements.java | 54 +++++++ .../presents/tools/streamable_as.tmpl | 48 ++++++ 7 files changed, 436 insertions(+), 93 deletions(-) create mode 100644 src/java/com/threerings/presents/tools/GenActionScriptStreamableTask.java create mode 100644 src/java/com/threerings/presents/tools/GeneratedSourceMerger.java create mode 100644 src/java/com/threerings/presents/tools/StreamableClassRequirements.java create mode 100644 src/java/com/threerings/presents/tools/streamable_as.tmpl diff --git a/src/java/com/threerings/presents/tools/GenActionScriptStreamableTask.java b/src/java/com/threerings/presents/tools/GenActionScriptStreamableTask.java new file mode 100644 index 000000000..e12113d9b --- /dev/null +++ b/src/java/com/threerings/presents/tools/GenActionScriptStreamableTask.java @@ -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 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 implemented = Sets.newLinkedHashSet(); + for (Class iface : sclass.getInterfaces()) { + implemented.add(addImportAndGetShortType(iface, false, imports)); + } + List 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 imports) + { + this.name = f.getName(); + this.simpleType = addImportAndGetShortType(f.getType(), true, imports); + this.reader = toReadObject(f.getType()); + this.writer = toWriteObject(f.getType(), name); + } + } +} diff --git a/src/java/com/threerings/presents/tools/GenActionScriptTask.java b/src/java/com/threerings/presents/tools/GenActionScriptTask.java index 484950d82..eaa5603a7 100644 --- a/src/java/com/threerings/presents/tools/GenActionScriptTask.java +++ b/src/java/com/threerings/presents/tools/GenActionScriptTask.java @@ -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 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('.'); } diff --git a/src/java/com/threerings/presents/tools/GenStreamableTask.java b/src/java/com/threerings/presents/tools/GenStreamableTask.java index 8da052cb7..36370208e 100644 --- a/src/java/com/threerings/presents/tools/GenStreamableTask.java +++ b/src/java/com/threerings/presents/tools/GenStreamableTask.java @@ -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(); diff --git a/src/java/com/threerings/presents/tools/GenTask.java b/src/java/com/threerings/presents/tools/GenTask.java index b3a9f1153..c66e76640 100644 --- a/src/java/com/threerings/presents/tools/GenTask.java +++ b/src/java/com/threerings/presents/tools/GenTask.java @@ -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; diff --git a/src/java/com/threerings/presents/tools/GeneratedSourceMerger.java b/src/java/com/threerings/presents/tools/GeneratedSourceMerger.java new file mode 100644 index 000000000..b901fccd9 --- /dev/null +++ b/src/java/com/threerings/presents/tools/GeneratedSourceMerger.java @@ -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 previouslyGenerated with marked sections updated from the same marked + * sections in newlyGenerated Everything outside these sections in + * previouslyGenerated is returned as is. A marked section starts with + * "// GENERATED START" and ends with "// GENERATED 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 sections = Maps.newLinkedHashMap(); + Matcher m = _sectionDelimiter.matcher(newlyGenerated); + while (m.find()) { + Tuple 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> secs = sections.entrySet().iterator(); + int currentStart = 0; + while (m.find()) { + merged.append(previouslyGenerated.substring(currentStart, m.start())); + Tuple 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 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 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. m is at the end of the section when this returns. + */ + protected Tuple 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"); +} diff --git a/src/java/com/threerings/presents/tools/StreamableClassRequirements.java b/src/java/com/threerings/presents/tools/StreamableClassRequirements.java new file mode 100644 index 000000000..54cd6bcc2 --- /dev/null +++ b/src/java/com/threerings/presents/tools/StreamableClassRequirements.java @@ -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 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); + } + } +} diff --git a/src/java/com/threerings/presents/tools/streamable_as.tmpl b/src/java/com/threerings/presents/tools/streamable_as.tmpl new file mode 100644 index 000000000..fe07d1066 --- /dev/null +++ b/src/java/com/threerings/presents/tools/streamable_as.tmpl @@ -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 + +} +} \ No newline at end of file