diff --git a/src/main/java/com/threerings/io/Streamable.java b/src/main/java/com/threerings/io/Streamable.java index 1c51611c6..0ed119169 100644 --- a/src/main/java/com/threerings/io/Streamable.java +++ b/src/main/java/com/threerings/io/Streamable.java @@ -25,9 +25,9 @@ package com.threerings.io; * Marks an object as streamable, meaning that it can be written to {@link ObjectOutputStream} * instances and read from {@link ObjectInputStream} instances. * - *
All non-{@code transient} fields will be automatically written and restored for a {@link - * Streamable} instance. Classes that wish to stream transient fields or customize the streaming - * process should implement methods with the following signatures:
+ *All non-{@code transient}, non-{@link NotStreamable} fields will be automatically written + * and restored for a {@link Streamable} instance. Classes that wish to stream transient fields or + * customize the streaming process should implement methods with the following signatures:
* *
* public void writeObject ({@link ObjectOutputStream} out);
@@ -38,6 +38,14 @@ package com.threerings.io;
* ObjectOutputStream#defaultWriteObject} and {@link ObjectInputStream#defaultReadObject} from
* within their {@code writeObject} and {@code readObject} methods to perform the standard
* streaming in addition to their customized behavior.
Streamable classes must either have a zero-argument constructor, in which case any + * number of other constructors are allowed, but the zero-argument constructor will be used when + * unserializing an instance; or they must have exactly one non-zero-argument constructor, and that + * constructor will be called when unserializing an instance with "zero/null" values (meaning all + * primitive types will be passed the appropriate zero value, and all reference types will be + * passed null. This latter approach can even be used by classes with final fields, as the + * zero/null values will be overwritten during unstreaming.
*/ public interface Streamable { diff --git a/src/main/java/com/threerings/io/Streamer.java b/src/main/java/com/threerings/io/Streamer.java index 9402c9af6..c0948ba1e 100644 --- a/src/main/java/com/threerings/io/Streamer.java +++ b/src/main/java/com/threerings/io/Streamer.java @@ -421,33 +421,26 @@ public abstract class Streamer } } - // if we're an anonymous closure, we also support having a single constructor to which - // we'll pass zero-valued arguments, which will then be overwritten by unstreaming - if (Streamable.Closure.class.isAssignableFrom(_target) && _ctor == null) { - Constructor>[] ctors = _target.getDeclaredConstructors(); - if (ctors.length > 1) { - throw new RuntimeException( - "Streamable closure classes must have either a zero-argument constructor " + - "or a single argument-taking constructor; multiple argument-taking " + - "constructors are not allowed [class=" + _target.getName() + "]"); - } - _ctor = ctors[0]; - _ctor.setAccessible(true); - - // we pass bogus arguments to it (because unstreaming will overwrite our bogus - // values with the real values) - Class>[] ptypes = _ctor.getParameterTypes(); - _ctorArgs = new Object[ptypes.length]; - for (int ii = 0; ii < ptypes.length; ii++) { - // this will be the appropriately typed zero, or null - _ctorArgs[ii] = Defaults.defaultValue(ptypes[ii]); - } + // otherwise there should be a single non-zero-argument constructor, which we'll call + // with zero-valued arguments at unstreaming time, which will then be overwritten by + // readObject() + Constructor>[] ctors = _target.getDeclaredConstructors(); + if (ctors.length > 1) { + throw new RuntimeException( + "Streamable closure classes must have either a zero-argument constructor " + + "or a single argument-taking constructor; multiple argument-taking " + + "constructors are not allowed [class=" + _target.getName() + "]"); } + _ctor = ctors[0]; + _ctor.setAccessible(true); - // make sure we found a constructor - if (_ctor == null) { - throw new RuntimeException("Unable to find applicable ctor " + - "[class=" + _target.getName() + "]"); + // we pass bogus arguments to it (because unstreaming will overwrite our bogus + // values with the real values) + Class>[] ptypes = _ctor.getParameterTypes(); + _ctorArgs = new Object[ptypes.length]; + for (int ii = 0; ii < ptypes.length; ii++) { + // this will be the appropriately typed zero, or null + _ctorArgs[ii] = Defaults.defaultValue(ptypes[ii]); } }