Allow any Streamable class to use the "we'll call your single non-zero-argument

constructor with zero/null values and then overwrite any fields initialized
with them immediately afterward" approach. If a Streamable has a zero-args
constructor, that is still used, so all the existing Streamable classes will
still function exactly as before. But new classes (or old classes undergoing
scrutiny) can opt to reduce their boilerplate as long as they know they cope
with the zero/null-valued constructor call.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6573 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2011-04-01 21:34:01 +00:00
parent 57d5d4ef0b
commit 56eeeb128d
2 changed files with 29 additions and 28 deletions
@@ -25,9 +25,9 @@ package com.threerings.io;
* Marks an object as streamable, meaning that it can be written to {@link ObjectOutputStream} * Marks an object as streamable, meaning that it can be written to {@link ObjectOutputStream}
* instances and read from {@link ObjectInputStream} instances. * instances and read from {@link ObjectInputStream} instances.
* *
* <p> All non-{@code transient} fields will be automatically written and restored for a {@link * <p> All non-{@code transient}, non-{@link NotStreamable} fields will be automatically written
* Streamable} instance. Classes that wish to stream transient fields or customize the streaming * and restored for a {@link Streamable} instance. Classes that wish to stream transient fields or
* process should implement methods with the following signatures: </p> * customize the streaming process should implement methods with the following signatures: </p>
* *
* <p><code> * <p><code>
* public void writeObject ({@link ObjectOutputStream} out); * public void writeObject ({@link ObjectOutputStream} out);
@@ -38,6 +38,14 @@ package com.threerings.io;
* ObjectOutputStream#defaultWriteObject} and {@link ObjectInputStream#defaultReadObject} from * ObjectOutputStream#defaultWriteObject} and {@link ObjectInputStream#defaultReadObject} from
* within their {@code writeObject} and {@code readObject} methods to perform the standard * within their {@code writeObject} and {@code readObject} methods to perform the standard
* streaming in addition to their customized behavior.</p> * streaming in addition to their customized behavior.</p>
*
* <p>Streamable classes must <em>either</em> 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.</p>
*/ */
public interface Streamable public interface Streamable
{ {
+18 -25
View File
@@ -421,33 +421,26 @@ public abstract class Streamer
} }
} }
// if we're an anonymous closure, we also support having a single constructor to which // otherwise there should be a single non-zero-argument constructor, which we'll call
// we'll pass zero-valued arguments, which will then be overwritten by unstreaming // with zero-valued arguments at unstreaming time, which will then be overwritten by
if (Streamable.Closure.class.isAssignableFrom(_target) && _ctor == null) { // readObject()
Constructor<?>[] ctors = _target.getDeclaredConstructors(); Constructor<?>[] ctors = _target.getDeclaredConstructors();
if (ctors.length > 1) { if (ctors.length > 1) {
throw new RuntimeException( throw new RuntimeException(
"Streamable closure classes must have either a zero-argument constructor " + "Streamable closure classes must have either a zero-argument constructor " +
"or a single argument-taking constructor; multiple argument-taking " + "or a single argument-taking constructor; multiple argument-taking " +
"constructors are not allowed [class=" + _target.getName() + "]"); "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]);
}
} }
_ctor = ctors[0];
_ctor.setAccessible(true);
// make sure we found a constructor // we pass bogus arguments to it (because unstreaming will overwrite our bogus
if (_ctor == null) { // values with the real values)
throw new RuntimeException("Unable to find applicable ctor " + Class<?>[] ptypes = _ctor.getParameterTypes();
"[class=" + _target.getName() + "]"); _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]);
} }
} }