Files
narya/aslib/src/main/as/com/threerings/io/ObjectInputStream.as
T
Michael Bayne 1c311431ad Finished the job of splitting Narya into submodules.
The ActionScript code is now under aslib and is built via Maven (and Ant). The
CPP code is under cpplib and is ignored by everything (other than service
generation), but now at least it's not crufting up the Java submodule with its
XCode project.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6783 542714f4-19e9-0310-aa3c-eee0fc999fb1
2012-02-14 19:11:33 +00:00

297 lines
9.2 KiB
ActionScript

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.io {
import flash.errors.IOError;
import flash.errors.MemoryError;
import flash.utils.ByteArray;
import flash.utils.IDataInput;
import com.threerings.util.ClassUtil;
import com.threerings.util.Log;
import com.threerings.util.Long;
public class ObjectInputStream
{
/** Enables verbose object I/O debugging. */
public static const DEBUG :Boolean = false;
public function ObjectInputStream (source :IDataInput = null, clientProps :Object = null)
{
_source = source || new ByteArray();
_cliProps = clientProps || {};
}
/**
* Set a new source from which to read our data.
*/
public function setSource (source :IDataInput) :void
{
_source = source;
}
/**
* Return a "client property" with the specified name.
* Actionscript only.
*/
public function getClientProperty (name :String) :*
{
return _cliProps[name];
}
/**
* Reads the next Object (or null) from the stream and returns it as * so that you may assign
* to any variable without casting. It is strongly recommended that you pass a 'checkType'
* parameter so that the type of the Object is verified to be what you believe it to be,
* thus helping you detect streaming errors as quickly as possible.
*
* @param checkType optional type to check the read object against
* @return the Object read, or null.
* @throws TypeError if the object is read successfully but is the wrong type
*
* @example This demonstrates how to type-check the objects read off the stream without
* having to cast them.
* <listing version="3.0">
*
* public function readObject (ins :ObjectInputStream) :void
* {
* _scoops = ins.readObject(Array);
* _coneType = ins.readObject(Cone);
* }
*
* protected var _scoops :Array;
* protected var _coneType :Cone;
* </listing>
*/
public function readObject (checkType :Class = null) :*
//throws IOError
{
var DEBUG_ID :String = "[" + (++_debugObjectCounter) + "] ";
try {
// read in the class code for this instance
var code :int = readShort();
// a zero code indicates a null value
if (code == 0) {
if (DEBUG) log.debug(DEBUG_ID + "Read null");
return null;
}
var cmap :ClassMapping;
// if the code is negative, that means we've never seen it
// before and class metadata follows
if (code < 0) {
// first swap the code into positive land
code *= -1;
// read in the class metadata
var jname :String = readUTF();
// log.debug("read jname: " + jname);
var streamer :Streamer = Streamer.getStreamerByJavaName(jname);
if (streamer == null) {
log.warning("OMG, cannot stream " + jname);
return null;
}
if (DEBUG) log.debug(DEBUG_ID + "Got streamer (" + streamer + ")");
cmap = new ClassMapping(code, streamer);
_classMap[code] = cmap;
if (DEBUG) log.debug(DEBUG_ID + "Created mapping: (" + code + "): " + jname);
} else {
cmap = (_classMap[code] as ClassMapping);
if (null == cmap) {
throw new IOError("Read object for which we have no " +
"registered class metadata [code=" + code + "].");
}
if (DEBUG) {
log.debug(DEBUG_ID + "Read known code: (" + code + ": " +
cmap.streamer.getJavaClassName() + ")");
}
}
// log.debug("Creating object sleeve...");
var target :Object = cmap.streamer.createObject(this);
//log.debug("Reading object...");
readBareObjectImpl(target, cmap.streamer);
if (DEBUG) log.debug(DEBUG_ID + "Read object: " + target);
if (checkType != null && !(target is checkType)) {
throw new TypeError(
"Cannot convert " + ClassUtil.getClass(target) + " to " + checkType);
}
return target;
} catch (me :MemoryError) {
throw new IOError("out of memory" + me.message);
}
return null; // not reached: compiler dumb
}
public function readBareObject (obj :Object) :void
//throws IOError
{
readBareObjectImpl(obj, Streamer.getStreamer(obj));
}
public function readBareObjectImpl (obj :Object, streamer :Streamer) :void
{
_current = obj;
_streamer = streamer;
try {
_streamer.readObject(obj, this);
} finally {
// clear out our current object references
_current = null;
_streamer = null;
}
}
/**
* Called to read an Object of a known final type into a Streamable object.
*
* @param type either a String representing the java type,
* a Class representing the actionscript type,
* or the Streamer to be used to read the field.
*/
public function readField (type :Object) :*
//throws IOError
{
if (!readBoolean()) {
return null;
}
var streamer :Streamer;
if (type is Streamer) {
streamer = Streamer(type);
} else {
var jname :String = type as String;
if (type is Class) {
jname = Translations.getToServer(ClassUtil.getClassName(type));
}
streamer= Streamer.getStreamerByJavaName(jname);
if (streamer == null) {
throw new Error("Cannot field stream " + type);
}
}
var obj :Object = streamer.createObject(this);
readBareObjectImpl(obj, streamer);
return obj;
}
public function defaultReadObject () :void
//throws IOError
{
_streamer.readObject(_current, this);
}
public function readBoolean () :Boolean
//throws IOError
{
return _source.readBoolean();
}
public function readByte () :int
//throws IOError
{
return _source.readByte();
}
/**
* Read bytes into the byte array. If length is not specified, then
* enough bytes to fill the array (from the offset) are read.
*/
public function readBytes (
bytes :ByteArray, offset :uint = 0, length :uint = uint.MAX_VALUE) :void
//throws IOError
{
// if no length specified then fill the ByteArray
if (length == uint.MAX_VALUE) {
length = bytes.length - offset;
}
// And, if we really want to read 0 bytes then just don't do anything, because an
// IDataInput will read *all available bytes* when the specified length is 0.
if (length > 0) {
_source.readBytes(bytes, offset, length);
}
}
public function readDouble () :Number
//throws IOError
{
return _source.readDouble();
}
public function readFloat () :Number
//throws IOError
{
return _source.readFloat();
}
public function readLong () :Long
{
const result :Long = new Long();
readBareObject(result);
return result;
}
public function readInt () :int
//throws IOError
{
return _source.readInt();
}
public function readShort () :int
//throws IOError
{
return _source.readShort();
}
public function readUTF () :String
//throws IOError
{
return _source.readUTF();
}
/** Named "client properties" that we can provide to deserialized objects. */
protected var _cliProps :Object;
/** The target DataInput that we route input from. */
protected var _source :IDataInput;
/** The object currently being read from the stream. */
protected var _current :Object;
/** The streamer being used currently. */
protected var _streamer :Streamer;
/** A map of short class code to ClassMapping info. */
protected var _classMap :Array = new Array();
private static var _debugObjectCounter :int = 0;
private static const log :Log = Log.getLog(ObjectInputStream);
}
}