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

org.red5.io.amf3.Output 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.lang.reflect.Array;
import java.lang.reflect.Field;
//import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//import java.util.Set;

//import net.sf.ehcache.Element;

//import org.apache.commons.beanutils.BeanMap;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.annotations.Anonymous;
//import org.red5.compatibility.flex.ObjectProxy;
import org.red5.io.amf.AMF;
import org.red5.io.object.RecordSet;
import org.red5.io.object.Serializer;
import org.red5.io.utils.XMLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

/**
 * AMF3 output writer
 *
 * @see  org.red5.io.amf3.AMF3
 * @see  org.red5.io.amf3.Input
 * @author The Red5 Project ([email protected])
 * @author Joachim Bauch ([email protected])
 * @author Harald Radi ([email protected])
 */
public class Output extends org.red5.io.amf.Output implements org.red5.io.object.Output {

	protected static Logger log = LoggerFactory.getLogger(Output.class);

	/**
	 * Set to a value above 0 to disable writing of the AMF3 object tag.
	 */
	private int amf3_mode;

	/**
	 * List of strings already written.
	 * */
	private Map stringReferences;

	/**
	 * Constructor of AMF3 output.
	 *
	 * @param buf instance of IoBuffer
	 * @see IoBuffer
	 */
	public Output(IoBuffer buf) {
		super(buf);
		amf3_mode = 0;
		stringReferences = new HashMap();
	}

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

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

	/** {@inheritDoc} */
	@Override
	public boolean supportsDataType(byte type) {
		return true;
	}

	// Basic Data Types

	protected void writeAMF3() {
		if (amf3_mode == 0) {
			buf.put(AMF.TYPE_AMF3_OBJECT);
		}
	}

	/** {@inheritDoc} */
	@Override
	public void writeBoolean(Boolean bol) {
		writeAMF3();
		buf.put(bol ? AMF3.TYPE_BOOLEAN_TRUE : AMF3.TYPE_BOOLEAN_FALSE);
	}

	/** {@inheritDoc} */
	@Override
	public void writeNull() {
		writeAMF3();
		buf.put(AMF3.TYPE_NULL);
	}

	protected void putInteger(long value) {
		if ((value >= -268435456) && (value <= 268435455)) {
			value &= 536870911;
		}
		if (value < 128) {
			buf.put((byte) value);
		} else if (value < 16384) {
			buf.put((byte) (((value >> 7) & 0x7F) | 0x80));
			buf.put((byte) (value & 0x7F));
		} else if (value < 2097152) {
			buf.put((byte) (((value >> 14) & 0x7F) | 0x80));
			buf.put((byte) (((value >> 7) & 0x7F) | 0x80));
			buf.put((byte) (value & 0x7F));
		} else if (value < 1073741824) {
			buf.put((byte) (((value >> 22) & 0x7F) | 0x80));
			buf.put((byte) (((value >> 15) & 0x7F) | 0x80));
			buf.put((byte) (((value >> 8) & 0x7F) | 0x80));
			buf.put((byte) (value & 0xFF));
		} else {
			log.error("Integer out of range: {}", value);
		}
	}

	protected static byte[] encodeString(String string) {
		//Element element = getStringCache().get(string);
		//byte[] encoded = (element == null ? null : (byte[]) element.getObjectValue());
		byte[] encoded = null;
		if (encoded == null) {
			ByteBuffer buf = AMF3.CHARSET.encode(string);
			encoded = new byte[buf.limit()];
			buf.get(encoded);
			//getStringCache().put(new Element(string, encoded));
		}
		return encoded;
	}

	protected void putString(String str, byte[] encoded) {
		final int len = encoded.length;
		Integer pos = stringReferences.get(str);
		if (pos != null) {
			// Reference to existing string
			putInteger(pos << 1);
			return;
		}

		putInteger(len << 1 | 1);
		buf.put(encoded);
		stringReferences.put(str, stringReferences.size());
	}

	/** {@inheritDoc} */
	@Override
	public void putString(String string) {
		if ("".equals(string)) {
			// Empty string;
			putInteger(1);
			return;
		}

		final byte[] encoded = encodeString(string);
		putString(string, encoded);
	}

	/** {@inheritDoc} */
	@Override
	public void writeNumber(Number num) {
		writeAMF3();
		if (num.longValue() < AMF3.MIN_INTEGER_VALUE || num.longValue() > AMF3.MAX_INTEGER_VALUE) {
			// Out of range for integer encoding
			buf.put(AMF3.TYPE_NUMBER);
			buf.putDouble(num.doubleValue());
		} else if (num instanceof Long || num instanceof Integer || num instanceof Short || num instanceof Byte) {
			buf.put(AMF3.TYPE_INTEGER);
			putInteger(num.longValue());
		} else {
			buf.put(AMF3.TYPE_NUMBER);
			buf.putDouble(num.doubleValue());
		}
	}

	/** {@inheritDoc} */
	@Override
	public void writeString(String string) {
		writeAMF3();
		buf.put(AMF3.TYPE_STRING);
		if ("".equals(string)) {
			putInteger(1);
		} else {
			final byte[] encoded = encodeString(string);
			putString(string, encoded);
		}
	}

	/** {@inheritDoc} */
	@Override
	public void writeDate(Date date) {
		writeAMF3();
		buf.put(AMF3.TYPE_DATE);
		if (hasReference(date)) {
			putInteger(getReferenceId(date) << 1);
			return;
		}

		storeReference(date);
		putInteger(1);
		buf.putDouble(date.getTime());
	}

	/** {@inheritDoc} */
	public void writeArray(Collection array, Serializer serializer) {
		writeAMF3();
		buf.put(AMF3.TYPE_ARRAY);
		if (hasReference(array)) {
			putInteger(getReferenceId(array) << 1);
			return;
		}

		storeReference(array);
		amf3_mode += 1;
		int count = array.size();
		putInteger(count << 1 | 1);
		putString("");
		for (Object item : array) {
			serializer.serialize(this, item);
		}
		amf3_mode -= 1;
	}

	/** {@inheritDoc} */
	public void writeArray(Object[] array, Serializer serializer) {
		writeAMF3();
		buf.put(AMF3.TYPE_ARRAY);
		if (hasReference(array)) {
			putInteger(getReferenceId(array) << 1);
			return;
		}

		storeReference(array);
		amf3_mode += 1;
		int count = array.length;
		putInteger(count << 1 | 1);
		putString("");
		for (Object item : array) {
			serializer.serialize(this, item);
		}
		amf3_mode -= 1;
	}

	/** {@inheritDoc} */
	public void writeArray(Object array, Serializer serializer) {
		writeAMF3();
		buf.put(AMF3.TYPE_ARRAY);
		if (hasReference(array)) {
			putInteger(getReferenceId(array) << 1);
			return;
		}

		storeReference(array);
		amf3_mode += 1;
		int count = Array.getLength(array);
		putInteger(count << 1 | 1);
		putString("");
		for (int i = 0; i < count; i++) {
			serializer.serialize(this, Array.get(array, i));
		}
		amf3_mode -= 1;
	}

	/** {@inheritDoc} */
	public void writeMap(Map map, Serializer serializer) {
		writeAMF3();
		buf.put(AMF3.TYPE_ARRAY);
		if (hasReference(map)) {
			putInteger(getReferenceId(map) << 1);
			return;
		}

		storeReference(map);
		// Search number of starting integer keys
		int count = 0;
		for (int i = 0; i < map.size(); i++) {
			try {
				if (!map.containsKey(i))
					break;
			} catch (ClassCastException err) {
				// Map has non-number keys.
				break;
			}
			count++;
		}

		amf3_mode += 1;
		if (count == map.size()) {
			// All integer keys starting from zero: serialize as regular array
			putInteger(count << 1 | 1);
			putString("");
			for (int i = 0; i < count; i++) {
				serializer.serialize(this, map.get(i));
			}
			amf3_mode -= 1;
			return;
		}

		putInteger(count << 1 | 1);
		// Serialize key-value pairs first
		for (Map.Entry entry : map.entrySet()) {
			Object key = entry.getKey();
			if ((key instanceof Number) && !(key instanceof Float) && !(key instanceof Double)
					&& ((Number) key).longValue() >= 0 && ((Number) key).longValue() < count) {
				// Entry will be serialized later
				continue;
			}
			putString(key.toString());
			serializer.serialize(this, entry.getValue());
		}
		putString("");
		// Now serialize integer keys starting from zero
		for (int i = 0; i < count; i++) {
			serializer.serialize(this, map.get(i));
		}
		amf3_mode -= 1;
	}

	/** {@inheritDoc} */
	public void writeMap(Collection array, Serializer serializer) {
		writeAMF3();
		buf.put(AMF3.TYPE_ARRAY);
		if (hasReference(array)) {
			putInteger(getReferenceId(array) << 1);
			return;
		}

		storeReference(array);
		// TODO: we could optimize this by storing the first integer
		//       keys after the key-value pairs
		amf3_mode += 1;
		putInteger(1);
		int idx = 0;
		for (Object item : array) {
			if (item != null) {
				putString(String.valueOf(idx));
				serializer.serialize(this, item);
			}
			idx++;
		}
		amf3_mode -= 1;
		putString("");
	}

	/** {@inheritDoc} */
	@Override
	protected void writeArbitraryObject(Object object, Serializer serializer) {
		Class objectClass = object.getClass();

		// If we need to serialize class information...
		if (!objectClass.isAnnotationPresent(Anonymous.class)) {
			putString(serializer.getClassName(objectClass));
		} else {
			putString("");
		}

		// Store key/value pairs
		amf3_mode += 1;
		// Iterate thru fields of an object to build "name-value" map from it
		for (Field field : objectClass.getFields()) {
			String fieldName = field.getName();

			log.debug("Field: {} class: {}", field, objectClass);
			// Check if the Field corresponding to the getter/setter pair is transient
			if (!serializeField(serializer, objectClass, fieldName, field, null)) {
				continue;
			}

			Object value;
			try {
				// Get field value
				value = field.get(object);
			} catch (IllegalAccessException err) {
				// Swallow on private and protected properties access exception
				continue;
			}

			// Write out prop name
			putString(fieldName);
			// Write out
			serializer.serialize(this, field, null, object, value);
		}
		amf3_mode -= 1;
		// Write out end of object marker
		putString("");
	}

	/** {@inheritDoc} */
//	@SuppressWarnings({ "rawtypes" })
	public void writeObject(Object object, Serializer serializer) {
/*
		writeAMF3();
		buf.put(AMF3.TYPE_OBJECT);
		if (hasReference(object)) {
			putInteger(getReferenceId(object) << 1);
			return;
		}

		storeReference(object);

		if (object instanceof IExternalizable) {
			// The object knows how to serialize itself.
			int type = 1 << 1 | 1;
			if (object instanceof ObjectProxy) {
				type |= AMF3.TYPE_OBJECT_PROXY << 2;
			} else {
				type |= AMF3.TYPE_OBJECT_EXTERNALIZABLE << 2;
			}
			putInteger(type);
			putString(serializer.getClassName(object.getClass()));
			amf3_mode += 1;
			((IExternalizable) object).writeExternal(new DataOutput(this, serializer));
			amf3_mode -= 1;
			return;
		}

		// We have an inline class that is not a reference.
		// We store the properties using key/value pairs
		int type = AMF3.TYPE_OBJECT_VALUE << 2 | 1 << 1 | 1;
		putInteger(type);
*/
		// Create new map out of bean properties
//		BeanMap beanMap = new BeanMap(object);
		// Set of bean attributes
//		Set set = beanMap.keySet();
//		if ((set.size() == 0) || (set.size() == 1 && beanMap.containsKey("class"))) {
//			// BeanMap is empty or can only access "class" attribute, skip it
//			writeArbitraryObject(object, serializer);
//			return;
//		}

		// Write out either start of object marker for class name or "empty" start of object marker
		Class objectClass = object.getClass();
		if (!objectClass.isAnnotationPresent(Anonymous.class)) {
			// classname
			putString(serializer.getClassName(object.getClass()));
		} else {
			putString("");
		}

		// Store key/value pairs
//		amf3_mode += 1;
//		for (Object key : set) {
//			String fieldName = key.toString();
//			log.debug("Field name: {} class: {}", fieldName, objectClass);
//
//			Field field = getField(objectClass, fieldName);
//			Method getter = getGetter(objectClass, beanMap, fieldName);
//
//			// Check if the Field corresponding to the getter/setter pair is transient
//			if (!serializeField(serializer, objectClass, fieldName, field, getter)) {
//				continue;
//			}
//
//			putString(fieldName);
//			serializer.serialize(this, field, getter, object, beanMap.get(key));
//		}
		amf3_mode -= 1;

		// End of object marker
		putString("");
	}

	/** {@inheritDoc} */
	@Override
	public void writeObject(Map map, Serializer serializer) {
		writeAMF3();
		buf.put(AMF3.TYPE_OBJECT);
		if (hasReference(map)) {
			putInteger(getReferenceId(map) << 1);
			return;
		}

		storeReference(map);
		// We have an inline class that is not a reference.
		// We store the properties using key/value pairs
		int type = AMF3.TYPE_OBJECT_VALUE << 2 | 1 << 1 | 1;
		putInteger(type);

		// No classname
		putString("");

		// Store key/value pairs
		amf3_mode += 1;
		for (Map.Entry entry : map.entrySet()) {
			putString(entry.getKey().toString());
			serializer.serialize(this, entry.getValue());
		}
		amf3_mode -= 1;

		// End of object marker
		putString("");
	}

	/** {@inheritDoc} */
	@Override
	public void writeRecordSet(RecordSet recordset, Serializer serializer) {
		writeString("Not implemented.");
	}

	/** {@inheritDoc} */
	@Override
	public void writeXML(Document xml) {
		writeAMF3();
		buf.put(AMF3.TYPE_XML);
		if (hasReference(xml)) {
			putInteger(getReferenceId(xml) << 1);
			return;
		}
		final byte[] encoded = encodeString(XMLUtils.docToString(xml));
		putInteger(encoded.length << 1 | 1);
		buf.put(encoded);
		storeReference(xml);
	}

	/** {@inheritDoc} */
	public void writeByteArray(ByteArray array) {
		writeAMF3();
		buf.put(AMF3.TYPE_BYTEARRAY);
		if (hasReference(array)) {
			putInteger(getReferenceId(array) << 1);
			return;
		}

		storeReference(array);
		IoBuffer data = array.getData();
		putInteger(data.limit() << 1 | 1);
		byte[] tmp = new byte[data.limit()];
		int old = data.position();
		try {
			data.position(0);
			data.get(tmp);
			buf.put(tmp);
		} finally {
			data.position(old);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy