From 410d85d49dc221ce47d8f3bb07488d989a05d313 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 4 May 2007 22:04:50 +0000 Subject: [PATCH] A task to generate readObject() and writeObject() for Streamable classes (for use in non-sandboxed environments). Unfortunately we have to do this for all Streamable classes, not just the ones that have protected/private members because we need to be able to call super.readObject() and have that exist even if our superclass contains only public members. Doing magic streaming with combinations of classes that do and don't have readObject() would start to get extremely complicated, particularly if the base class had readObject() an intermediate class had none, then a later class had one again. Madness. Doing things with compiled methods will be faster anyway... git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4686 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- build.xml | 8 + .../presents/tools/GenStreamableTask.java | 249 ++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/java/com/threerings/presents/tools/GenStreamableTask.java diff --git a/build.xml b/build.xml index 4fb8f7220..829c9adbc 100644 --- a/build.xml +++ b/build.xml @@ -85,6 +85,14 @@ + + + + + + + readObject() and writeObject() 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 Task +{ + /** + * Adds a nested <fileset> element which enumerates streamable source files. + */ + public void addFileset (FileSet set) + { + _filesets.add(set); + } + + /** + * Performs the actual work of the task. + */ + public void execute () throws BuildException + { + for (FileSet fs : _filesets) { + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + File fromDir = fs.getDir(getProject()); + String[] srcFiles = ds.getIncludedFiles(); + for (int f = 0; f < srcFiles.length; f++) { + processClass(new File(fromDir, srcFiles[f])); + } + } + } + + /** + * 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. + */ + protected void processClass (File source, Class sclass) + throws IOException + { + // we must implement Streamable, not be a DObject and not be an interface ourselves + if (!Streamable.class.isAssignableFrom(sclass) || DObject.class.isAssignableFrom(sclass) || + Modifier.isInterface(sclass.getModifiers())) { + // System.err.println("Skipping " + sclass.getName() + "..."); + return; + } + + // add readObject() and writeObject() definitions + StringBuffer readbuf = new StringBuffer(READ_OPEN); + StringBuffer writebuf = new StringBuffer(WRITE_OPEN); + + // see if our parent also implements Streamable + Class supster = sclass.getSuperclass(); + do { + if (isStreamable(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; + } + readbuf.append(" "); + readbuf.append(field.getName()).append(" = "); + readbuf.append(toReadObject(field)); + readbuf.append(";\n"); + + writebuf.append(" out."); + writebuf.append(toWriteObject(field)); + writebuf.append(";\n"); + added++; + } + if (added == 0) { + return; // nothing to do + } + + readbuf.append(READ_CLOSE); + writebuf.append(WRITE_CLOSE); + + System.err.println("Converting " + sclass.getName() + "..."); + + SourceFile sfile = new SourceFile(); + try { + sfile.readFrom(source); + // don't overwrite an existing readObject() or writeObject() + String readsec = + sfile.containsString("public void readObject") ? "" : readbuf.toString(); + String writesec = + sfile.containsString("public void writeObject") ? "" : writebuf.toString(); + sfile.writeTo(source, readsec, writesec); + } catch (IOException ioe) { + System.err.println("Error processing " + source + ": " + ioe); + } + } + + protected boolean isStreamable (Class clazz) + { + for (Class iface : clazz.getInterfaces()) { + if (Streamable.class.equals(iface)) { + return true; + } + } + 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(); + 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 _filesets = new ArrayList(); + + 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"; +}