blob: 0e62b88e0056e463eba6b63dcdb162346e14cc2c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.io.amf;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import flex.messaging.io.PropertyProxy;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.SerializationException;
import flex.messaging.io.UnknownTypeException;
import flex.messaging.io.amf.AmfTrace.VectorType;
import flex.messaging.util.ClassUtil;
import flex.messaging.util.Trace;
/**
* Reads AMF 3 formatted data stream.
* <p>
* This class intends to matches the Flash Player 8 C++ code
* in avmglue/DataIO.cpp
* </p>
*
*
*/
public class Amf3Input extends AbstractAmfInput implements Amf3Types
{
/**
*
*/
protected List objectTable;
/**
*
*/
protected List stringTable;
/**
*
*/
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.
*/
@Override
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.
* @return Object the object read
* @throws ClassNotFoundException, IOException when reading object process failed
*/
public Object readObject() throws ClassNotFoundException, IOException
{
int type = in.readByte();
Object value = readObjectValue(type);
return value;
}
/**
*
*/
protected Object readObjectValue(int type) throws ClassNotFoundException, IOException
{
Object value = null;
switch (type)
{
case kStringType:
ClassUtil.validateCreation(String.class);
value = readString();
if (isDebug)
trace.writeString((String)value);
break;
case kObjectType:
value = readScriptObject();
break;
case kArrayType:
value = readArray();
break;
case kFalseType:
ClassUtil.validateCreation(Boolean.class);
value = Boolean.FALSE;
if (isDebug)
trace.write(value);
break;
case kTrueType:
ClassUtil.validateCreation(Boolean.class);
value = Boolean.TRUE;
if (isDebug)
trace.write(value);
break;
case kIntegerType:
ClassUtil.validateCreation(Integer.class);
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 = Double.valueOf(readDouble());
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();
break;
case kByteArrayType:
value = readByteArray();
break;
case kDictionaryType:
value = readDictionary();
break;
case kTypedVectorInt:
case kTypedVectorUint:
case kTypedVectorDouble:
case kTypedVectorObject:
value = readTypedVector(type);
break;
default:
// Unknown object type tag {type}
UnknownTypeException ex = new UnknownTypeException();
ex.setMessage(10301, new Object[]{new Integer(type)});
throw ex;
}
return value;
}
/** {@inheritDoc} */
@Override
public double readDouble() throws IOException
{
ClassUtil.validateCreation(Double.class);
double d = super.readDouble();
if (isDebug)
trace.write(d);
return d;
}
/**
*
*/
protected String readString() throws IOException
{
int ref = readUInt29();
if ((ref & 1) == 0) // This is a reference
return getStringReference(ref >> 1);
int len = (ref >> 1); // Read the string in
// writeString() special cases the empty string
// to avoid creating a reference.
if (0 == len)
return EMPTY_STRING;
String str = readUTF(len);
stringTable.add(str); // Remember String
return str;
}
/**
* Deserialize the bits of a date-time value w/o a prefixing type byte.
*/
protected Date readDate() throws IOException
{
ClassUtil.validateCreation(Date.class);
int ref = readUInt29();
if ((ref & 1) == 0) // This is a reference
return (Date)getObjectReference(ref >> 1);
long time = (long)in.readDouble();
Date d = new Date(time);
objectTable.add(d); //Remember Date
if (isDebug)
trace.write(d);
return d;
}
protected Object readDictionary() throws IOException, ClassNotFoundException
{
int ref = readUInt29();
if ((ref & 1) == 0) // This is a reference.
return getObjectReference(ref >> 1);
readBoolean(); // usingWeakTypes - irrelevant in Java.
int len = (ref >> 1);
Dictionary dictionary = (Hashtable)ClassUtil.createDefaultInstance(Hashtable.class, null, true /*validate*/);
objectTable.add(dictionary); // Remember the object.
if (isDebug)
trace.startAMFDictionary(objectTable.size() - 1);
for (int i = 0; i < len; i++)
{
if (isDebug) trace.startDictionaryElement();
Object key = readObjectOneLevelDown(true);
if (isDebug) trace.addDictionaryEquals();
Object value = readObjectOneLevelDown(true);
ClassUtil.validateAssignment(dictionary, key != null? key.toString() : null, value);
dictionary.put(key, value);
}
if (isDebug)
trace.endAMFDictionary();
return dictionary;
}
protected Object readTypedVector(int type) throws IOException, ClassNotFoundException
{
int ref = readUInt29();
if ((ref & 1) == 0) // This is a reference.
return getObjectReference(ref >> 1);
int len = (ref >> 1);
boolean fixed = readBoolean();
Object vector = null;
switch (type)
{
case kTypedVectorInt:
vector = readTypedIntVector(len, fixed);
break;
case kTypedVectorUint:
vector = readTypedUintVector(len, fixed);
break;
case kTypedVectorDouble:
vector = readTypedDoubleVector(len, fixed);
break;
case kTypedVectorObject:
vector = readTypedObjectVector(len, fixed);
break;
default:
// Unknown object type tag {type}
UnknownTypeException ex = new UnknownTypeException();
ex.setMessage(10301, new Object[]{Integer.valueOf(type)});
throw ex;
}
return vector;
}
@SuppressWarnings("unchecked")
protected Object readTypedIntVector(int len, boolean fixed) throws IOException
{
// Don't instantiate Array right away with the supplied size if it is more
// than INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered.
boolean useListTemporarily = false;
Object vector;
if (fixed)
{
useListTemporarily = len > INITIAL_COLLECTION_CAPACITY;
if (useListTemporarily)
{
ClassUtil.validateCreation(ArrayList.class);
vector = new ArrayList<Integer>(INITIAL_COLLECTION_CAPACITY);
}
else
{
ClassUtil.validateCreation(Integer[].class);
vector = new Integer[len];
}
}
else
{
ClassUtil.validateCreation(ArrayList.class);
int initialCapacity = len < INITIAL_COLLECTION_CAPACITY? len : INITIAL_COLLECTION_CAPACITY;
vector = new ArrayList<Integer>(initialCapacity);
}
int objectId = rememberObject(vector);
if (isDebug)
trace.startAMFVector(objectTable.size() - 1, VectorType.INT);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
ClassUtil.validateCreation(Integer.class);
int value = readInt();
if (isDebug)
trace.write(value);
Integer item = Integer.valueOf(value);
ClassUtil.validateAssignment(vector, i, item);
if (vector instanceof Integer[])
Array.set(vector, i, item);
else
((List<Integer>)vector).add(item);
}
if (useListTemporarily)
{
vector = ((ArrayList<Integer>)vector).toArray();
objectTable.set(objectId, vector);
}
if (isDebug)
trace.endAMFVector();
return vector;
}
@SuppressWarnings("unchecked")
protected Object readTypedUintVector(int len, boolean fixed) throws IOException
{
// Don't instantiate Array right away with the supplied size if it is more
// than INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered.
boolean useListTemporarily = false;
Object vector;
if (fixed)
{
useListTemporarily = len > INITIAL_COLLECTION_CAPACITY;
if (useListTemporarily)
{
ClassUtil.validateCreation(ArrayList.class);
vector = new ArrayList<Long>(INITIAL_COLLECTION_CAPACITY);
}
else
{
ClassUtil.validateCreation(Long[].class);
vector = new Long[len];
}
}
else
{
ClassUtil.validateCreation(ArrayList.class);
int initialCapacity = len < INITIAL_COLLECTION_CAPACITY? len : INITIAL_COLLECTION_CAPACITY;
vector = new ArrayList<Long>(initialCapacity);
}
int objectId = rememberObject(vector);
if (isDebug)
trace.startAMFVector(objectTable.size() - 1, VectorType.UINT);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
ClassUtil.validateCreation(Long.class);
long value = (long) (in.readByte() & 0xFF) << 24L;
value += (long) (in.readByte() & 0xFF) << 16L;
value += (long) (in.readByte() & 0xFF) << 8L;
value += (in.readByte() & 0xFF);
if (isDebug)
trace.write(value);
Long item = Long.valueOf(value);
ClassUtil.validateAssignment(vector, i, item);
if (vector instanceof Long[])
Array.set(vector, i, item);
else
((List<Long>)vector).add(item);
}
if (useListTemporarily)
{
vector = ((ArrayList<Long>)vector).toArray();
objectTable.set(objectId, vector);
}
if (isDebug)
trace.endAMFVector();
return vector;
}
@SuppressWarnings("unchecked")
protected Object readTypedDoubleVector(int len, boolean fixed) throws IOException
{
// Don't instantiate Array right away with the supplied size if it is more
// than INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered.
boolean useListTemporarily = false;
Object vector;
if (fixed)
{
useListTemporarily = len > INITIAL_COLLECTION_CAPACITY;
if (useListTemporarily)
{
ClassUtil.validateCreation(ArrayList.class);
vector = new ArrayList<Double>(INITIAL_COLLECTION_CAPACITY);
}
else
{
ClassUtil.validateCreation(Double[].class);
vector = new Double[len];
}
}
else
{
ClassUtil.validateCreation(ArrayList.class);
int initialCapacity = len < INITIAL_COLLECTION_CAPACITY? len : INITIAL_COLLECTION_CAPACITY;
vector = new ArrayList<Double>(initialCapacity);
}
int objectId = rememberObject(vector);
if (isDebug)
trace.startAMFVector(objectTable.size() - 1, VectorType.DOUBLE);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Double item = Double.valueOf(readDouble());
ClassUtil.validateAssignment(vector, i, item);
if (vector instanceof Double[])
Array.set(vector, i, item);
else
((List<Double>)vector).add(item);
}
if (useListTemporarily)
{
vector = ((ArrayList<Double>)vector).toArray();
objectTable.set(objectId, vector);
}
if (isDebug)
trace.endAMFVector();
return vector;
}
@SuppressWarnings("unchecked")
protected Object readTypedObjectVector(int len, boolean fixed) throws IOException, ClassNotFoundException
{
// TODO - Class name is always empty for some reason, need to figure out if this is expected.
String className = readString();
if (className == null || className.length() == 0 || className.equals(EMPTY_STRING))
className = Object.class.getName();
// Don't instantiate Array right away with the supplied size if it is more
// than INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered.
boolean useListTemporarily = false;
Object vector;
if (fixed)
{
useListTemporarily = len > INITIAL_COLLECTION_CAPACITY;
if (useListTemporarily)
{
ClassUtil.validateCreation(ArrayList.class);
vector = new ArrayList<Object>(INITIAL_COLLECTION_CAPACITY);
}
else
{
ClassUtil.validateCreation(Object[].class);
vector = new Object[len];
}
}
else
{
ClassUtil.validateCreation(ArrayList.class);
int initialCapacity = len < INITIAL_COLLECTION_CAPACITY? len : INITIAL_COLLECTION_CAPACITY;
vector = new ArrayList<Object>(initialCapacity);
}
int objectId = rememberObject(vector);
if (isDebug)
trace.startAMFVector(objectTable.size() - 1, VectorType.OBJECT);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Object item = readObjectOneLevelDown(true);
ClassUtil.validateAssignment(vector, i, item);
if (vector instanceof Object[])
Array.set(vector, i, item);
else
((List<Object>)vector).add(item);
}
if (useListTemporarily)
{
vector = ((ArrayList<Object>)vector).toArray();
objectTable.set(objectId, vector);
}
if (isDebug)
trace.endAMFVector();
return vector;
}
/**
*
*/
protected Object readArray() throws ClassNotFoundException, IOException
{
int ref = readUInt29();
if ((ref & 1) == 0) // This is a reference.
return getObjectReference(ref >> 1);
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 = (HashMap)ClassUtil.createDefaultInstance(HashMap.class, null, true /*validate*/);
array = map;
//Remember Object
objectTable.add(array);
if (isDebug)
trace.startECMAArray(objectTable.size() - 1);
}
if (isDebug)
trace.namedElement(name);
Object value = readObjectOneLevelDown(true);
ClassUtil.validateAssignment(map, name, value);
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)
{
// Don't instantiate List/Array right away with the supplied size if it is more than
// INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered. This at least
// requires the user to pass in the actual objects for the List/Array to grow beyond.
boolean useListTemporarily = false;
// Legacy Flex 1.5 behavior was to return a java.util.Collection for Array
if (context.legacyCollection || len > INITIAL_COLLECTION_CAPACITY)
{
useListTemporarily = !context.legacyCollection;
ClassUtil.validateCreation(ArrayList.class);
int initialCapacity = len < INITIAL_COLLECTION_CAPACITY? len : INITIAL_COLLECTION_CAPACITY;
array = new ArrayList(initialCapacity);
}
// Flex 2+ behavior is to return Object[] for AS3 Array
else
{
ClassUtil.validateCreation(Object[].class);
array = new Object[len];
}
int objectId = rememberObject(array); // Remember the List/Object[].
if (isDebug)
trace.startAMFArray(objectTable.size() - 1);
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Object item = readObjectOneLevelDown(true);
ClassUtil.validateAssignment(array, i, item);
if (array instanceof ArrayList)
((ArrayList)array).add(item);
else
Array.set(array, i, item);
}
if (useListTemporarily)
{
array = ((ArrayList)array).toArray();
objectTable.set(objectId, array);
}
}
else
{
for (int i = 0; i < len; i++)
{
if (isDebug)
trace.arrayElement(i);
Object item = readObjectOneLevelDown(true);
String key = Integer.toString(i);
ClassUtil.validateAssignment(map, key, item);
map.put(key, item);
}
}
if (isDebug)
trace.endAMFArray();
return array;
}
/**
*
*/
protected Object readScriptObject() throws ClassNotFoundException, IOException
{
int ref = readUInt29();
if ((ref & 1) == 0)
return getObjectReference(ref >> 1);
TraitsInfo ti = readTraits(ref);
String className = ti.getClassName();
boolean externalizable = ti.isExternalizable();
// Prepare the parameters for createObjectInstance(). Use an array as a holder
// to simulate two 'by-reference' parameters className and (initially null) proxy
Object[] params = new Object[] {className, null};
Object object = createObjectInstance(params);
// Retrieve any changes to the className and the proxy parameters
className = (String)params[0];
PropertyProxy proxy = (PropertyProxy)params[1];
// Remember our instance in the object table
int objectId = rememberObject(object);
if (externalizable)
{
readExternalizable(className, object);
}
else
{
if (isDebug)
{
trace.startAMFObject(className, objectTable.size() - 1);
}
boolean isCollectionClass = isCollectionClass(object);
int len = ti.getProperties().size();
for (int i = 0; i < len; i++)
{
String propName = ti.getProperty(i);
if (isDebug)
trace.namedElement(propName);
Object value = readObjectOneLevelDown(isCollectionClass);
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 = readObjectOneLevelDown(isCollectionClass);
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;
}
/**
*
*/
protected void readExternalizable(String className, Object object) throws ClassNotFoundException, IOException
{
if (object instanceof Externalizable)
{
ClassUtil.validateCreation(Externalizable.class);
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;
}
}
/**
*
*/
protected byte[] readByteArray() throws IOException
{
ClassUtil.validateCreation(byte[].class);
int ref = readUInt29();
if ((ref & 1) == 0)
return (byte[])getObjectReference(ref >> 1);
int len = (ref >> 1);
int initialCapacity = len < INITIAL_COLLECTION_CAPACITY? len : INITIAL_COLLECTION_CAPACITY;
ByteArrayOutputStream outStream = new ByteArrayOutputStream(initialCapacity);
for (int i = 0; i < len; i++)
outStream.write(in.read());
byte[] ba = outStream.toByteArray();
objectTable.add(ba);
if (isDebug)
trace.startByteArray(objectTable.size() - 1, len);
return ba;
}
/**
*
*/
protected TraitsInfo readTraits(int ref) throws IOException
{
if ((ref & 3) == 1) // This is a reference
return getTraitReference(ref >> 2);
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;
}
/**
*
*/
protected String readUTF(int utflen) throws IOException
{
checkUTFLength(utflen);
// We should just read the bytes into a buffer
byte[] bytearr = new byte[utflen];
in.readFully(bytearr, 0, utflen);
// It is UTF-8 encoding, directly use new String(bytes, "utf-8");
String s = new String(bytearr, "utf-8");
return s;
}
/**
* 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.
* <pre>
* 0x00000000 - 0x0000007F : 0xxxxxxx
* 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
* 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
* 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
* 0x40000000 - 0xFFFFFFFF : throw range exception
* </pre>
*
* @return A int capable of holding an unsigned 29 bit integer.
* @throws IOException
*
*/
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);
}
/**
*
*/
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 = (String)ClassUtil.createDefaultInstance(String.class, null);
else
xml = readUTF(len);
//Remember Object
objectTable.add(xml);
if (isDebug)
trace.write(xml);
}
return stringToDocument(xml);
}
/**
*
*/
protected Object getObjectReference(int ref)
{
if (isDebug)
trace.writeRef(ref);
return objectTable.get(ref);
}
/**
*
*/
protected String getStringReference(int ref)
{
String str = (String)stringTable.get(ref);
if (Trace.amf && isDebug)
trace.writeStringRef(ref);
return str;
}
/**
*
*/
protected TraitsInfo getTraitReference(int ref)
{
if (Trace.amf && isDebug)
trace.writeTraitsInfoRef(ref);
return (TraitsInfo)traitsTable.get(ref);
}
/**
* Remember a deserialized object so that you can use it later through a reference.
*/
protected int rememberObject(Object obj)
{
int id = objectTable.size();
objectTable.add(obj);
return id;
}
protected Object readObjectOneLevelDown(boolean nestCollectionLevelDown) throws ClassNotFoundException, IOException
{
increaseNestObjectLevel();
if (nestCollectionLevelDown)
increaseNestCollectionLevel();
Object value = readObject();
decreaseNestObjectLevel();
if (nestCollectionLevelDown)
decreaseNestCollectionLevel();
return value;
}
}