/************************************************************************* * * ADOBE CONFIDENTIAL * __________________ * * Copyright 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.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. *
* This class intends to matches the Flash Player 8 C++ code * in avmglue/DataIO.cpp *
* * @author Peter Farland * @exclude */ 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, context.createASObjectForMissingType); 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. *
* 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);
}
}