All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.io.amf3.Input Maven / Gradle / Ivy

The newest version!
package org.red5.io.amf3;

/*
 * RED5 Open Source Flash Server - http://code.google.com/p/red5/
 * 
 * Copyright (c) 2006-2010 by respective authors (see below). All rights reserved.
 * 
 * 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 
 */

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
//import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

//import org.apache.commons.beanutils.BeanUtils;
//import org.apache.commons.beanutils.BeanUtilsBean;
//import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.amf.AMF;
import org.red5.io.object.DataTypes;
import org.red5.io.object.Deserializer;
import org.red5.io.utils.ArrayUtils;
import org.red5.io.utils.ObjectMap;
import org.red5.io.utils.XMLUtils;
import org.red5.server.service.ConversionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

/**
 * Input for Red5 data (AMF3) types
 *
 * @author The Red5 Project ([email protected])
 * @author Luke Hubbard, Codegent Ltd ([email protected])
 * @author Joachim Bauch ([email protected])
 */
public class Input extends org.red5.io.amf.Input implements org.red5.io.object.Input {

//	private static ConvertUtilsBean convertUtilsBean = BeanUtilsBean.getInstance().getConvertUtils();

	/**
	 * Holds informations about already deserialized classes.
	 */
	protected static class ClassReference {
		/** Name of the deserialized class. */
		protected String className;

		/** Type of the class. */
		protected int type;

		/** Names of the attributes of the class. */
		protected List attributeNames;

		/** Create new informations about a class. 
		 * @param className class name
		 * @param type type
		 * @param attributeNames attributes
		 */
		public ClassReference(String className, int type, List attributeNames) {
			this.className = className;
			this.type = type;
			this.attributeNames = attributeNames;
		}
	}

	/**
	 * Dummy class that is stored as reference for objects currently
	 * being deserialized that reference themselves. 
	 */
	protected static class PendingObject {

		static final class PendingProperty {
			Object obj;

			Class klass;

			String name;

			PendingProperty(Object obj, Class klass, String name) {
				this.obj = obj;
				this.klass = klass;
				this.name = name;
			}
		}

		private List properties;

		public void addPendingProperty(Object obj, Class klass, String name) {
			if (properties == null) {
				properties = new ArrayList();
			}
			properties.add(new PendingProperty(obj, klass, name));
		}

		public void resolveProperties(Object result) {
			if (properties == null)
				// No pending properties
				return;

			for (PendingProperty prop : properties) {
				try {
					prop.klass.getField(prop.name).set(prop.obj, result);
				} catch (Exception e) {
					try {
//						BeanUtils.setProperty(prop.obj, prop.name, result);
					} catch (Exception ex) {
						log.error("Error mapping property: {} ({})", prop.name, result);
					}
				}
			}
			properties.clear();
		}
	}

	/**
	 * Class used to collect AMF3 references.
	 * In AMF3 references should be collected through the whole "body" (across several Input objects).
	 */
	public static class RefStorage {
		private List classReferences = new ArrayList();

		private List stringReferences = new ArrayList();

		private Map refMap = new HashMap();
	}

	/**
	 * Logger
	 */
	protected static Logger log = LoggerFactory.getLogger(Input.class);

	/**
	 * Set to a value above 0 to enforce AMF3 decoding mode.
	 */
	private int amf3_mode;

	/**
	 * List of string values found in the input stream.
	 */
	private List stringReferences;

	/**
	 * Informations about already deserialized classes.
	 */
	private List classReferences;

	/**
	 * Creates Input object for AMF3 from byte buffer
	 * 
	 * @param buf        Byte buffer
	 */
	public Input(IoBuffer buf) {
		super(buf);
		amf3_mode = 0;
		stringReferences = new ArrayList();
		classReferences = new ArrayList();
	}

	/**
	 * Creates Input object for AMF3 from byte buffer and initializes references
	 * from passed RefStorage
	 * @param buf buffer
	 * @param refStorage ref storage
	 */
	public Input(IoBuffer buf, RefStorage refStorage) {
		super(buf);
		this.stringReferences = refStorage.stringReferences;
		this.classReferences = refStorage.classReferences;
		this.refMap = refStorage.refMap;
		amf3_mode = 0;
	}

	/**
	 * Force using AMF3 everywhere
	 */
	public void enforceAMF3() {
		amf3_mode++;
	}

	/**
	 * Provide access to raw data.
	 * 
	 * @return IoBuffer
	 */
	protected IoBuffer getBuffer() {
		return buf;
	}

	/**
	 * Reads the data type
	 * 
	 * @return byte      Data type
	 */
	@Override
	public byte readDataType() {
		byte coreType = AMF3.TYPE_UNDEFINED;
		if (buf != null) {
			currentDataType = buf.get();
			log.debug("Current data type: {}", currentDataType);

			if (currentDataType == AMF.TYPE_AMF3_OBJECT) {
				currentDataType = buf.get();
			} else if (amf3_mode == 0) {
				// AMF0 object
				return readDataType(currentDataType);
			}

			log.debug("Current data type (after amf checks): {}", currentDataType);

			switch (currentDataType) {
				case AMF3.TYPE_UNDEFINED:
				case AMF3.TYPE_NULL:
					coreType = DataTypes.CORE_NULL;
					break;
				case AMF3.TYPE_INTEGER:
				case AMF3.TYPE_NUMBER:
					coreType = DataTypes.CORE_NUMBER;
					break;

				case AMF3.TYPE_BOOLEAN_TRUE:
				case AMF3.TYPE_BOOLEAN_FALSE:
					coreType = DataTypes.CORE_BOOLEAN;
					break;

				case AMF3.TYPE_STRING:
					coreType = DataTypes.CORE_STRING;
					break;
				// TODO check XML_SPECIAL
				case AMF3.TYPE_XML:
				case AMF3.TYPE_XML_DOCUMENT:
					coreType = DataTypes.CORE_XML;
					break;
				case AMF3.TYPE_OBJECT:
					coreType = DataTypes.CORE_OBJECT;
					break;

				case AMF3.TYPE_ARRAY:
					// should we map this to list or array?
					coreType = DataTypes.CORE_ARRAY;
					break;

				case AMF3.TYPE_DATE:
					coreType = DataTypes.CORE_DATE;
					break;

				case AMF3.TYPE_BYTEARRAY:
					coreType = DataTypes.CORE_BYTEARRAY;
					break;

				case AMF3.TYPE_VECTOR_INT:
					coreType = DataTypes.CORE_VECTOR_INT;
					break;
				case AMF3.TYPE_VECTOR_UINT:
					coreType = DataTypes.CORE_VECTOR_UINT;
					break;
				case AMF3.TYPE_VECTOR_NUMBER:
					coreType = DataTypes.CORE_VECTOR_NUMBER;
					break;
				case AMF3.TYPE_VECTOR_OBJECT:
					coreType = DataTypes.CORE_VECTOR_OBJECT;
					break;

				default:
					log.info("Unknown datatype: {}", currentDataType);
					// End of object, and anything else lets just skip
					coreType = DataTypes.CORE_SKIP;
					break;
			}
			log.debug("Core type: {}", coreType);
		} else {
			log.error("Why is buf null?");
		}
		return coreType;
	}

	// Basic

	/**
	 * Reads a null (value)
	 * 
	 * @return Object    null
	 */
	@Override
	public Object readNull(Type target) {
		return null;
	}

	/**
	 * Reads a boolean
	 * 
	 * @return boolean     Boolean value
	 */
	@Override
	public Boolean readBoolean(Type target) {
		return (currentDataType == AMF3.TYPE_BOOLEAN_TRUE) ? Boolean.TRUE : Boolean.FALSE;
	}

	/**
	 * Reads a Number
	 * 
	 * @return Number      Number
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Number readNumber(Type target) {
		Number v;

		if (currentDataType == AMF3.TYPE_NUMBER) {
			v = buf.getDouble();
		} else {
			// we are decoding an int
			v = readAMF3Integer();
		}

		if (target instanceof Class && Number.class.isAssignableFrom((Class) target)) {
			Class cls = (Class) target;
			if (!cls.isAssignableFrom(v.getClass())) {
//				v = (Number) convertUtilsBean.convert(v.toString(), cls);
			}
		}

		return v;
	}

	/**
	 * Reads a string
	 * 
	 * @return String       String
	 */
	@Override
	public String readString(Type target) {
		int len = readAMF3Integer();
		log.debug("readString - length: {}", len);
		if (len == 1) {
			// Empty string
			return "";
		}
		if ((len & 1) == 0) {
			//if the refs are empty an IndexOutOfBoundsEx will be thrown
			if (stringReferences.isEmpty()) {
				log.debug("String reference list is empty");
			}
			// Reference
			return stringReferences.get(len >> 1);
		}
		len >>= 1;
		log.debug("readString - new length: {}", len);
		int limit = buf.limit();
		log.debug("readString - limit: {}", limit);
		final ByteBuffer strBuf = buf.buf();
		strBuf.limit(strBuf.position() + len);
		final String string = AMF3.CHARSET.decode(strBuf).toString();
		log.debug("String: {}", string);
		buf.limit(limit); // Reset the limit
		stringReferences.add(string);
		return string;
	}

	/**
	 * Reads a string of a set length. This does not use
	 * the string reference table.
	 * 
	 * @param length the length of the string
	 * @return String
	 */
	public String readString(int length) {
		log.debug("readString - length: {}", length);
		int limit = buf.limit();
		final ByteBuffer strBuf = buf.buf();
		strBuf.limit(strBuf.position() + length);
		final String string = AMF3.CHARSET.decode(strBuf).toString();
		log.debug("String: {}", string);
		buf.limit(limit);
		//check for null termination
		byte b = buf.get();
		if (b != 0) {
			buf.position(buf.position() - 1);
		}
		return string;
	}

	public String getString() {
		return readString(String.class);
	}

	/**
	 * Returns a date
	 * 
	 * @return Date        Date object
	 */
	@Override
	public Date readDate(Type target) {
		int ref = readAMF3Integer();
		if ((ref & 1) == 0) {
			// Reference to previously found date
			return (Date) getReference(ref >> 1);
		}

		long ms = (long) buf.getDouble();
		Date date = new Date(ms);
		storeReference(date);
		return date;
	}

	// Array

	/**
	 * Returns an array
	 * 
	 * @return int        Length of array
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object readArray(Deserializer deserializer, Type target) {
		int count = readAMF3Integer();
		log.debug("Count: {} and {} ref {}", new Object[] { count, (count & 1), (count >> 1) });
		if ((count & 1) == 0) {
			//Reference
			Object ref = getReference(count >> 1);
			if (ref != null) {
				return ref;
			}
		}

		count = (count >> 1);
		String key = readString(String.class);
		amf3_mode += 1;
		Object result;
		if (key.equals("")) {
			Class nested = Object.class;
			Class collection = Collection.class;
			Collection resultCollection;

			if (target instanceof ParameterizedType) {
				ParameterizedType t = (ParameterizedType) target;
				Type[] actualTypeArguments = t.getActualTypeArguments();
				if (actualTypeArguments.length == 1) {
					nested = (Class) actualTypeArguments[0];
				}
				target = t.getRawType();
			}

			if (target instanceof Class) {
				collection = (Class) target;
			}

			if (collection.isArray()) {
				nested = ArrayUtils.getGenericType(collection.getComponentType());
				result = Array.newInstance(nested, count);
				storeReference(result);
				for (int i = 0; i < count; i++) {
					final Object value = deserializer.deserialize(this, nested);
					Array.set(result, i, value);
				}
			} else {
				if (SortedSet.class.isAssignableFrom(collection)) {
					resultCollection = new TreeSet();
				} else if (Set.class.isAssignableFrom(collection)) {
					resultCollection = new HashSet(count);
				} else {
					resultCollection = new ArrayList(count);
				}

				result = resultCollection;
				storeReference(result);

				for (int i = 0; i < count; i++) {
					final Object value = deserializer.deserialize(this, nested);
					resultCollection.add(value);
				}

			}
		} else {
			Class k = Object.class;
			Class v = Object.class;
			Class collection = Collection.class;

			if (target instanceof ParameterizedType) {
				ParameterizedType t = (ParameterizedType) target;
				Type[] actualTypeArguments = t.getActualTypeArguments();
				if (actualTypeArguments.length == 2) {
					k = (Class) actualTypeArguments[0];
					v = (Class) actualTypeArguments[1];
				}
				target = t.getRawType();
			}

			if (target instanceof Class) {
				collection = (Class) target;
			}

			if (SortedMap.class.isAssignableFrom(collection)) {
				collection = TreeMap.class;
			} else {
				collection = HashMap.class;
			}

			Map resultMap;

			try {
				resultMap = (Map) collection.newInstance();
			} catch (Exception e) {
				resultMap = new HashMap(count);
			}

			// associative array
			storeReference(resultMap);
			while (!key.equals("")) {
				final Object value = deserializer.deserialize(this, v);
				resultMap.put(key, value);
				key = readString(k);
			}
			for (int i = 0; i < count; i++) {
				final Object value = deserializer.deserialize(this, v);
				resultMap.put(i, value);
			}
			result = resultMap;
		}
		amf3_mode -= 1;
		return result;
	}

	public Object readMap(Deserializer deserializer, Type target) {
		throw new RuntimeException("AMF3 doesn't support maps.");
	}

	// Object

	@SuppressWarnings({ "unchecked", "rawtypes", "serial" })
	public Object readObject(Deserializer deserializer, Type target) {
		int type = readAMF3Integer();
		log.debug("Type: {} and {} ref {}", new Object[] { type, (type & 1), (type >> 1) });
		if ((type & 1) == 0) {
			//Reference
			Object ref = getReference(type >> 1);
			if (ref != null) {
				return ref;
			}
			byte b = buf.get();
			if (b == 7) {
				log.debug("BEL: {}", b); //7			
			} else {
				log.debug("Non-BEL byte: {}", b);
				log.debug("Extra byte: {}", buf.get());
			}
		}

		type >>= 1;
		List attributes = null;
		String className;
		Object result = null;
		boolean inlineClass = (type & 1) == 1;
		log.debug("Class is in-line? {}", inlineClass);
		if (!inlineClass) {
			ClassReference info = classReferences.get(type >> 1);
			className = info.className;
			attributes = info.attributeNames;
			type = info.type;
			if (attributes != null) {
				type |= attributes.size() << 2;
			}
		} else {
			type >>= 1;
			className = readString(String.class);
			log.debug("Type: {} classname: {}", type, className);
			//check for flex class alias since these wont be detected as externalizable
			if (classAliases.containsKey(className)) {
				//make sure type is externalizable
				type = 1;
			} else if (className.startsWith("flex")) {
				//set the attributes for messaging classes
				if (className.endsWith("CommandMessage")) {
					attributes = new LinkedList() {
						{
							add("timestamp");
							add("headers");
							add("operation");
							add("body");
							add("correlationId");
							add("messageId");
							add("timeToLive");
							add("clientId");
							add("destination");
						}
					};
				} else {
					log.debug("Attributes for {} were not set", className);
				}
			}
		}
		amf3_mode += 1;
		Object instance = newInstance(className);
		Map properties = null;
		PendingObject pending = new PendingObject();
		int tempRefId = storeReference(pending);
		log.debug("Object type: {}", (type & 0x03));
		switch (type & 0x03) {
			case AMF3.TYPE_OBJECT_PROPERTY:
				log.debug("Detected: Object property type");
				// Load object properties into map
				int count = type >> 2;
				log.debug("Count: {}", count);
				if (attributes == null) {
					attributes = new ArrayList(count);
					for (int i = 0; i < count; i++) {
						attributes.add(readString(String.class));
					}
					classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_PROPERTY, attributes));
				}
				properties = new ObjectMap();
				for (int i = 0; i < count; i++) {
					String name = attributes.get(i);
					properties.put(name, deserializer.deserialize(this, getPropertyType(instance, name)));
				}
				break;
			case AMF3.TYPE_OBJECT_EXTERNALIZABLE:
				log.debug("Detected: Externalizable type");
				// Use custom class to deserialize the object
				if ("".equals(className)) {
					throw new RuntimeException("Classname is required to load an Externalizable object");
				}
				log.debug("Externalizable class: {}", className);
				if (className.length() == 3) {
					//check for special DS class aliases
					className = classAliases.get(className);
				}
				result = newInstance(className);
				if (result == null) {
					throw new RuntimeException(String.format("Could not instantiate class: %s", className));
				}
				if (!(result instanceof IExternalizable)) {
					throw new RuntimeException(String.format("Class must implement the IExternalizable interface: %s",
							className));
				}
				classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_EXTERNALIZABLE, null));
				storeReference(tempRefId, result);
				((IExternalizable) result).readExternal(new DataInput(this, deserializer));
				break;
			case AMF3.TYPE_OBJECT_VALUE:
				log.debug("Detected: Object value type");
				// First, we should read typed (non-dynamic) properties ("sealed traits" according to AMF3 specification).
				// Property names are stored in the beginning, then values are stored.
				count = type >> 2;
				log.debug("Count: {}", count);
				if (attributes == null) {
					attributes = new ArrayList(count);
					for (int i = 0; i < count; i++) {
						attributes.add(readString(String.class));
					}
					classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_VALUE, attributes));
				}
				//use the size of the attributes if we have no count
				if (count == 0 && attributes != null) {
					count = attributes.size();
					log.debug("Using class attribute size for property count: {}", count);
					//read the attributes from the stream and log if count doesnt match
					List tmpAttributes = new ArrayList(count);
					for (int i = 0; i < count; i++) {
						tmpAttributes.add(readString(String.class));
					}
					if (count != tmpAttributes.size()) {
						log.debug("Count and attributes length does not match!");
					}
					classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_VALUE, attributes));
				}

				properties = new ObjectMap();
				for (String key : attributes) {
					log.debug("Looking for property: {}", key);
					Object value = deserializer.deserialize(this, getPropertyType(instance, key));
					log.debug("Key: {} Value: {}", key, value);
					properties.put(key, value);
				}

				log.trace("Buffer - position: {} limit: {}", buf.position(), buf.limit());
				//no more items to read if we are at the end of the buffer
				if (buf.position() < buf.limit()) {
					// Now we should read dynamic properties which are stored as name-value pairs.
					// Dynamic properties are NOT remembered in 'classReferences'.
					String key = readString(String.class);
					while (!"".equals(key)) {
						Object value = deserializer.deserialize(this, getPropertyType(instance, key));
						properties.put(key, value);
						key = readString(String.class);
					}
				}
				break;
			default:
			case AMF3.TYPE_OBJECT_PROXY:
				log.debug("Detected: Object proxy type");
				if ("".equals(className)) {
					throw new RuntimeException("Classname is required to load an Externalizable object");
				}
				log.debug("Externalizable class: {}", className);
				result = newInstance(className);
				if (result == null) {
					throw new RuntimeException(String.format("Could not instantiate class: %s", className));
				}
				if (!(result instanceof IExternalizable)) {
					throw new RuntimeException(String.format("Class must implement the IExternalizable interface: %s",
							className));
				}
				classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_PROXY, null));
				storeReference(tempRefId, result);
				((IExternalizable) result).readExternal(new DataInput(this, deserializer));
		}
		amf3_mode -= 1;

		if (result == null) {
			// Create result object based on classname
			if ("".equals(className)) {
				// "anonymous" object, load as Map
				// Resolve circular references
				for (Map.Entry entry : properties.entrySet()) {
					if (entry.getValue() == pending) {
						entry.setValue(properties);
					}
				}

				storeReference(tempRefId, properties);
				result = properties;
			} else if ("RecordSet".equals(className)) {
				// TODO: how are RecordSet objects encoded?
				throw new RuntimeException("Objects of type RecordSet not supported yet.");
			} else if ("RecordSetPage".equals(className)) {
				// TODO: how are RecordSetPage objects encoded?
				throw new RuntimeException("Objects of type RecordSetPage not supported yet.");
			} else {
				// Apply properties to object
				result = newInstance(className);
				if (result != null) {
					storeReference(tempRefId, result);
					Class resultClass = result.getClass();
					pending.resolveProperties(result);
					for (Map.Entry entry : properties.entrySet()) {
						// Resolve circular references
						final String key = entry.getKey();
						Object value = entry.getValue();
						if (value == pending) {
							value = result;
						}

						if (value instanceof PendingObject) {
							// Defer setting of value until real object is created
							((PendingObject) value).addPendingProperty(result, resultClass, key);
							continue;
						}

						if (value != null) {
							try {
								final Field field = resultClass.getField(key);
								final Class fieldType = field.getType();
								if (!fieldType.isAssignableFrom(value.getClass())) {
									value = ConversionUtils.convert(value, fieldType);
								} else if (value instanceof Enum) {
									value = Enum.valueOf(fieldType, value.toString());
								}
								field.set(result, value);
							} catch (Exception e) {
//								try {
////									BeanUtils.setProperty(result, key, value);
////								} catch (IllegalAccessException ex) {
////									log.warn("Error mapping key: {} value: {}", key, value);
//								} catch (InvocationTargetException ex) {
//									log.warn("Error mapping key: {} value: {}", key, value);
//								}
							}
						} else {
							log.debug("Skipping null property: {}", key);
						}
					}
				} // else fall through
			}
		}
		return result;
	}

	public ByteArray readByteArray(Type target) {
		int type = readAMF3Integer();
		if ((type & 1) == 0) {
			// Reference
			return (ByteArray) getReference(type >> 1);
		}

		type >>= 1;
		ByteArray result = new ByteArray(buf, type);
		storeReference(result);
		return result;
	}

	@SuppressWarnings("unchecked")
	public List readVectorInt() {
		int type = readAMF3Integer();
		if ((type & 1) == 0) {
			return (List) getReference(type >> 1);
		}

		int len = type >> 1;
		List array = new ArrayList(len);

		storeReference(array);

		@SuppressWarnings("unused")
		int ref2 = readAMF3Integer();
		for (int j = 0; j < len; ++j) {
			array.add(buf.getInt());
		}

		return array;
	}

	@SuppressWarnings("unchecked")
	public List readVectorUInt() {
		int type = readAMF3Integer();
		if ((type & 1) == 0) {
			return (List) getReference(type >> 1);
		}

		int len = type >> 1;
		List array = new ArrayList(len);

		storeReference(array);

		@SuppressWarnings("unused")
		int ref2 = readAMF3Integer();
		for (int j = 0; j < len; ++j) {
			long value = (buf.get() & 0xff) << 24L;
			value += (buf.get() & 0xff) << 16L;
			value += (buf.get() & 0xff) << 8L;
			value += (buf.get() & 0xff);
			array.add(value);
		}

		return array;
	}

	@SuppressWarnings("unchecked")
	public List readVectorNumber() {
		int type = readAMF3Integer();
		if ((type & 1) == 0) {
			return (List) getReference(type >> 1);
		}

		int len = type >> 1;
		List array = new ArrayList(len);

		storeReference(array);

		@SuppressWarnings("unused")
		int ref2 = readAMF3Integer();
		for (int j = 0; j < len; ++j) {
			array.add(buf.getDouble());
		}

		return array;
	}

	@SuppressWarnings("unchecked")
	public List readVectorObject() {
		int type = readAMF3Integer();
		if ((type & 1) == 0) {
			return (List) getReference(type >> 1);
		}

		int len = type >> 1;
		List array = new ArrayList(len);

		storeReference(array);
		
		Deserializer deserializer = new Deserializer();
		Object object;

		for (int j = 0; j < len; ++j) {
			object = readObject(deserializer, null);
			array.add(object);
		}

		return array;
	}	
	
	// Others

	/**
	 * Reads Custom
	 * 
	 * @return Object     Custom type object
	 */
	@Override
	public Object readCustom(Type target) {
		// Return null for now
		return null;
	}

	/** {@inheritDoc} */
	public Object readReference(Type target) {
		throw new RuntimeException("AMF3 doesn't support direct references.");
	}

	/**
	 * Resets map
	 */
	@Override
	public void reset() {
		super.reset();
		stringReferences.clear();
	}

	/**
	 * Parser of AMF3 "compressed" integer data type
	 * 
	 * @return a converted integer value
	 * @see parsing AMF3
	 *      integers (external)
	 */
	private int readAMF3Integer() {
		int n = 0;
		int b = buf.get();
		int result = 0;

		while ((b & 0x80) != 0 && n < 3) {
			result <<= 7;
			result |= (b & 0x7f);
			b = buf.get();
			n++;
		}
		if (n < 3) {
			result <<= 7;
			result |= b;
		} else {
			/* Use all 8 bits from the 4th byte */
			result <<= 8;
			result |= b & 0x0ff;

			/* Check if the integer should be negative */
			if ((result & 0x10000000) != 0) {
				/* and extend the sign bit */
				result |= 0xe0000000;
			}
		}

		return result;
	}
	
	//Read UInt29 style
	@SuppressWarnings("unused")
	private int readAMF3IntegerNew() {
		int b = buf.get() & 0xFF;
		if (b < 128) {
			return b;
		}
		int value = (b & 0x7F) << 7;
		b = buf.get() & 0xFF;
		if (b < 128) {
			return (value | b);
		}
		value = (value | b & 0x7F) << 7;
		b = buf.get() & 0xFF;
		if (b < 128) {
			return (value | b);
		}
		value = (value | b & 0x7F) << 8;
		b = buf.get() & 0xFF;
		return (value | b);
	}	

	/** {@inheritDoc} */
	public Document readXML(Type target) {
		int len = readAMF3Integer();
		if (len == 1) {
			// Empty string, should not happen
			return null;
		}
		if ((len & 1) == 0) {
			// Reference
			return (Document) getReference(len >> 1);
		}
		len >>= 1;
		int limit = buf.limit();
		final ByteBuffer strBuf = buf.buf();
		strBuf.limit(strBuf.position() + len);
		final String xmlString = AMF3.CHARSET.decode(strBuf).toString();
		buf.limit(limit); // Reset the limit
		Document doc = null;
		try {
			doc = XMLUtils.stringToDoc(xmlString);
		} catch (IOException ioex) {
			log.error("IOException converting xml to dom", ioex);
		}
		storeReference(doc);
		return doc;
	}

}