/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* [2002] - [2007] Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated
* and its suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*/
package flex.messaging.io.amf;
import flex.messaging.io.AbstractProxy;
import flex.messaging.io.ClassAliasRegistry;
import flex.messaging.io.PropertyProxy;
import flex.messaging.io.PropertyProxyRegistry;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.SerializationException;
import flex.messaging.io.UnknownTypeException;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.util.Trace;
import flex.messaging.util.ClassUtil;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.io.Externalizable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Reads AMF 3 formatted data stream
* <p>
* This class intends to matches the Flash Player 8 C++ code
* in avmglue/DataIO.cpp
* </p>
*
* @author Peter Farland
*/
public class Amf3Input extends AbstractAmfInput implements Amf3Types
{
/**
* @exclude
*/
protected List objectTable;
/**
* @exclude
*/
protected List stringTable;
/**
* @exclude
*/
protected List traitsTable;
public Amf3Input(SerializationContext context)
{
super(context);
stringTable = new ArrayList(64);
objectTable = new ArrayList(64);
traitsTable = new ArrayList(10);
}
/**
* Reset should be called before reading a top level object,
* such as a new header or a new body.
*/
public void reset()
{
super.reset();
stringTable.clear();
objectTable.clear();
traitsTable.clear();
}
public Object saveObjectTable()
{
Object table = objectTable;
objectTable = new ArrayList(64);
return table;
}
public void restoreObjectTable(Object table)
{
objectTable = (ArrayList) table;
}
public Object saveTraitsTable()
{
Object table = traitsTable;
traitsTable = new ArrayList(10);
return table;
}
public void restoreTraitsTable(Object table)
{
traitsTable = (ArrayList) table;
}
public Object saveStringTable()
{
Object table = stringTable;
stringTable = new ArrayList(64);
return table;
}
public void restoreStringTable(Object table)
{
stringTable = (ArrayList) table;
}
/**
* Public entry point to read a top level AMF Object, such as
* a header value or a message body.
*/
public Object readObject() throws ClassNotFoundException, IOException
{
int type = in.readByte();
Object value = readObjectValue(type);
return value;
}
/**
* @exclude
*/
protected Object readObjectValue(int type) throws ClassNotFoundException, IOException
{
Object value = null;
switch (type)
{
case kStringType:
value = readString();
if (isDebug)
trace.writeString((String)value);
break;
case kObjectType:
value = readScriptObject();
break;
case kArrayType:
value = readArray();
break;
case kFalseType:
value = Boolean.FALSE;
if (isDebug)
trace.write(value);
break;
case kTrueType:
value = Boolean.TRUE;
if (isDebug)
trace.write(value);
break;
case kIntegerType:
int i = readUInt29();
// Symmetric with writing an integer to fix sign bits for negative values...
i = (i << 3) >> 3;
value = new Integer(i);
if (isDebug)
trace.write(value);
break;
case kDoubleType:
value = new Double(in.readDouble());
if (isDebug)
trace.write(value);
break;
case kUndefinedType:
if (isDebug)
trace.writeUndefined();
break;
case kNullType:
if (isDebug)
trace.writeNull();
break;
case kXMLType:
case kAvmPlusXmlType:
value = readXml();
break;
case kDateType:
value = readDate();
if (isDebug)
trace.write(value.toString());
break;
case kByteArrayType:
value = readByteArray();
break;
default:
// Unknown object type tag {type}
UnknownTypeException ex = new UnknownTypeException();
ex.setMessage(10301, new Object[]{new Integer(type)});
throw ex;
}
return value;
}
/**
* @exclude
*/
protected String readString() throws IOException
{
int ref = readUInt29();
if ((ref & 1) == 0)
{
// This is a reference
return getStringReference(ref >> 1);
}
else
{
// Read the string in
int len = (ref >> 1);
// writeString() special cases the empty string
// to avoid creating a reference.
if (0 == len)
{
return EMPTY_STRING;
}
String str = readUTF(len);
// Remember String
stringTable.add(str);
return str;
}
}
/**
* Deserialize the bits of a date-time value w/o a prefixing type byte
*/
protected Date readDate() throws IOException
{
int ref = readUInt29();
if ((ref & 1) == 0)
{
// This is a reference
return (Date)getObjectReference(ref >> 1);
}
else
{
long time = (long)in.readDouble();
Date d = new Date(time);
//Remember Date
objectTable.add(d);
if (isDebug)
trace.write(d);
return d;
}
}
/**
* @exclude
*/
protected Object readArray() throws ClassNotFoundException, IOException
{
int ref = readUInt29();
if ((ref & 1) == 0)
{
// This is a reference
return getObjectReference(ref >> 1);
}
else
{
int len = (ref >> 1);
Object array = null;
// First, look for any string based keys. If any
// non-ordinal indices were used, or if the Array is
// sparse, we represent the structure as a Map.
Map map = null;
for (; ;)
{
String name = readString();
if (name == null || name.length() == 0) break;
if (map == null)
{
map = new HashMap();
array = map;
//Remember Object
objectTable.add(array);
if (isDebug)
trace.startECMAArray(objectTable.size() - 1);
}
Object value = readObject();
map.put(name, value);
}
// If we didn't find any string based keys, we have a
// dense Array, so we represent the structure as a List.
if (map == null)
{
// Legacy Flex 1.5 behavior was to return a java.util.Collection for Array
if (context.legacyCollection)
{
List list = new ArrayList(len);
array = list;
// Remember List
objectTable.add(array);
if (isDebug)
trace.startAMFArray(objectTable.size() - 1);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Object item = readObject();
list.add(i, item);
}
}
else
{
// New Flex 2+ behavior is to return Object[] for AS3 Array
array = new Object[len];
// Remember native Object[]
objectTable.add(array);
if (isDebug)
trace.startAMFArray(objectTable.size() - 1);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Object item = readObject();
Array.set(array, i, item);
}
}
}
else
{
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Object item = readObject();
map.put(Integer.toString(i), item);
}
}
if (isDebug)
trace.endAMFArray();
return array;
}
}
/**
* @exclude
*/
protected Object readScriptObject() throws ClassNotFoundException, IOException
{
int ref = readUInt29();
if ((ref & 1) == 0)
{
return getObjectReference(ref >> 1);
}
else
{
TraitsInfo ti = readTraits(ref);
String className = ti.getClassName();
boolean externalizable = ti.isExternalizable();
Object object;
PropertyProxy proxy = null;
// Check for any registered class aliases
String aliasedClass = ClassAliasRegistry.getRegistry().getClassName(className);
if (aliasedClass != null)
className = aliasedClass;
if (className == null || className.length() == 0)
{
object = new ASObject();
}
else if (className.startsWith(">")) // Handle [RemoteClass] (no server alias)
{
object = new ASObject();
((ASObject)object).setType(className);
}
else if (context.instantiateTypes || className.startsWith("flex."))
{
Class desiredClass = AbstractProxy.getClassFromClassName(className);
proxy = PropertyProxyRegistry.getRegistry().getProxyAndRegister(desiredClass);
if (proxy == null)
object = ClassUtil.createDefaultInstance(desiredClass, null);
else
object = proxy.createInstance(className);
}
else
{
// Just return type info with an ASObject...
object = new ASObject();
((ASObject)object).setType(className);
}
if (proxy == null)
proxy = PropertyProxyRegistry.getProxyAndRegister(object);
// Remember our instance in the object table
int objectId = objectTable.size();
objectTable.add(object);
if (externalizable)
{
readExternalizable(className, object);
}
else
{
if (isDebug)
{
trace.startAMFObject(className, objectTable.size() - 1);
}
int len = ti.getProperties().size();
for (int i = 0; i < len; i++)
{
String propName = (String)ti.getProperty(i);
if (isDebug)
trace.namedElement(propName);
Object value = readObject();
proxy.setValue(object, propName, value);
}
if (ti.isDynamic())
{
for (; ;)
{
String name = readString();
if (name == null || name.length() == 0) break;
if (isDebug)
trace.namedElement(name);
Object value = readObject();
proxy.setValue(object, name, value);
}
}
}
if (isDebug)
trace.endAMFObject();
// This lets the BeanProxy substitute a new instance into the BeanProxy
// at the end of the serialization. You might for example create a Map, store up
// the properties, then construct the instance based on that. Note that this does
// not support recursive references to the parent object however.
Object newObj = proxy.instanceComplete(object);
// TODO: It is possible we gave out references to the
// temporary object. it would be possible to warn users about
// that problem by tracking if we read any references to this object
// in the readObject call above.
if (newObj != object)
{
objectTable.set(objectId, newObj);
object = newObj;
}
return object;
}
}
/**
* @exclude
*/
protected void readExternalizable(String className, Object object) throws ClassNotFoundException, IOException
{
if (object instanceof Externalizable)
{
if (isDebug)
{
trace.startExternalizableObject(className, objectTable.size() - 1);
}
((Externalizable)object).readExternal(this);
}
else
{
//Class '{className}' must implement java.io.Externalizable to receive client IExternalizable instances.
SerializationException ex = new SerializationException();
ex.setMessage(10305, new Object[] {object.getClass().getName()});
throw ex;
}
}
/**
* @exclude
*/
protected byte[] readByteArray() throws IOException
{
int ref = readUInt29();
if ((ref & 1) == 0)
{
return (byte[])getObjectReference(ref >> 1);
}
else
{
int len = (ref >> 1);
byte[] ba = new byte[len];
// Remember byte array object
objectTable.add(ba);
in.readFully(ba, 0, len);
if (isDebug)
trace.startByteArray(objectTable.size() - 1, len);
return ba;
}
}
/**
* @exclude
*/
protected TraitsInfo readTraits(int ref) throws IOException
{
if ((ref & 3) == 1)
{
// This is a reference
return getTraitReference(ref >> 2);
}
else
{
boolean externalizable = ((ref & 4) == 4);
boolean dynamic = ((ref & 8) == 8);
int count = (ref >> 4); /* uint29 */
String className = readString();
TraitsInfo ti = new TraitsInfo(className, dynamic, externalizable, count);
// Remember Trait Info
traitsTable.add(ti);
for (int i = 0; i < count; i++)
{
String propName = readString();
ti.addProperty(propName);
}
return ti;
}
}
/**
* @exclude
*/
protected String readUTF(int utflen) throws IOException
{
char[] charr = getTempCharArray(utflen);
byte[] bytearr = getTempByteArray(utflen);
int c, char2, char3;
int count = 0;
int chCount = 0;
in.readFully(bytearr, 0, utflen);
while (count < utflen)
{
c = (int)bytearr[count] & 0xff;
switch (c >> 4)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
/* 0xxxxxxx*/
count++;
charr[chCount] = (char)c;
break;
case 12:
case 13:
/* 110x xxxx 10xx xxxx*/
count += 2;
if (count > utflen)
throw new UTFDataFormatException();
char2 = (int)bytearr[count - 1];
if ((char2 & 0xC0) != 0x80)
throw new UTFDataFormatException();
charr[chCount] = (char)(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
/* 1110 xxxx 10xx xxxx 10xx xxxx */
count += 3;
if (count > utflen)
throw new UTFDataFormatException();
char2 = (int)bytearr[count - 2];
char3 = (int)bytearr[count - 1];
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
throw new UTFDataFormatException();
charr[chCount] = (char)
(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
default:
/* 10xx xxxx, 1111 xxxx */
throw new UTFDataFormatException();
}
chCount++;
}
// The number of chars produced may be less than utflen
return new String(charr, 0, chCount);
}
/**
* AMF 3 represents smaller integers with fewer bytes using the most
* significant bit of each byte. The worst case uses 32-bits
* to represent a 29-bit number, which is what we would have
* done with no compression.
* <p/>
* 0x00000000 - 0x0000007F : 0xxxxxxx
* 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
* 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
* 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
* 0x40000000 - 0xFFFFFFFF : throw range exception
*
* @return A int capable of holding an unsigned 29 bit integer.
* @throws IOException
* @exclude
*/
protected int readUInt29() throws IOException
{
int value;
// Each byte must be treated as unsigned
int b = in.readByte() & 0xFF;
if (b < 128)
{
return b;
}
value = (b & 0x7F) << 7;
b = in.readByte() & 0xFF;
if (b < 128)
{
return (value | b);
}
value = (value | (b & 0x7F)) << 7;
b = in.readByte() & 0xFF;
if (b < 128)
{
return (value | b);
}
value = (value | (b & 0x7F)) << 8;
b = in.readByte() & 0xFF;
return (value | b);
}
/**
* @exclude
*/
protected Object readXml() throws IOException
{
String xml = null;
int ref = readUInt29();
if ((ref & 1) == 0)
{
// This is a reference
xml = (String)getObjectReference(ref >> 1);
}
else
{
// Read the string in
int len = (ref >> 1);
// writeString() special case the empty string
// for speed. Do add a reference
if (0 == len)
xml = EMPTY_STRING;
else
xml = readUTF(len);
//Remember Object
objectTable.add(xml);
if (isDebug)
trace.write(xml);
}
return stringToDocument(xml);
}
/**
* @exclude
*/
protected Object getObjectReference(int ref)
{
if (isDebug)
{
trace.writeRef(ref);
}
return objectTable.get(ref);
}
/**
* @exclude
*/
protected String getStringReference(int ref)
{
String str = (String)stringTable.get(ref);
if (Trace.amf && isDebug)
{
trace.writeStringRef(ref);
}
return str;
}
/**
* @exclude
*/
protected TraitsInfo getTraitReference(int ref)
{
if (Trace.amf && isDebug)
{
trace.writeTraitsInfoRef(ref);
}
return (TraitsInfo)traitsTable.get(ref);
}
}