//
// $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.
*
*
* public function readObject (ins :ObjectInputStream) :void
* {
* _scoops = ins.readObject(Array);
* _coneType = ins.readObject(Cone);
* }
*
* protected var _scoops :Array;
* protected var _coneType :Cone;
*
*/
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);
}
}