Java tutorial
package org.red5.io.amf3; /* * RED5 Open Source Flash Server - http://www.osflash.org/red5 * * Copyright (c) 2006-2007 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.Field; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.apache.mina.common.ByteBuffer; import org.red5.io.amf.AMF; import org.red5.io.object.DataTypes; import org.red5.io.object.Deserializer; 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 (red5@osflash.org) * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) * @author Joachim Bauch (jojo@struktur.de) */ public class Input extends org.red5.io.amf.Input implements org.red5.io.object.Input { /** * Holds informations about already deserialized classes. */ protected 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<String> attributeNames; /** Create new informations about a class. */ public ClassReference(String className, int type, List<String> 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 class PendingObject { 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<PendingProperty> properties; public void addPendingProperty(Object obj, Class<?> klass, String name) { if (properties == null) { properties = new ArrayList<PendingProperty>(); } properties.add(new PendingProperty(obj, klass, name)); } public void resolveProperties(Object result) { if (properties == null) // No pending properties return; for (PendingProperty prop : properties) { try { try { prop.klass.getField(prop.name).set(prop.obj, result); } catch (Exception e) { BeanUtils.setProperty(prop.obj, prop.name, result); } } catch (Exception e) { log.error("Error mapping property: {} ({})", prop.name, result); } } properties.clear(); } } /** * Logger */ protected static Logger log = LoggerFactory.getLogger(Input.class); /** * Set to a value above <tt>0</tt> to enforce AMF3 decoding mode. */ private int amf3_mode; /** * List of string values found in the input stream. */ private List<String> stringReferences; /** * Informations about already deserialized classes. */ private List<ClassReference> classReferences; /** * Creates Input object for AMF3 from byte buffer * * @param buf Byte buffer */ public Input(ByteBuffer buf) { super(buf); amf3_mode = 0; stringReferences = new ArrayList<String>(); classReferences = new ArrayList<ClassReference>(); } /** * Provide access to raw data. * * @return ByteBuffer */ protected ByteBuffer getBuffer() { return buf; } /** * Reads the data type * * @return byte Data type */ @Override public byte readDataType() { if (buf == null) { log.error("Why is buf null?"); } currentDataType = buf.get(); byte coreType; if (currentDataType == AMF.TYPE_AMF3_OBJECT) { currentDataType = buf.get(); } else if (amf3_mode == 0) { // AMF0 object return readDataType(currentDataType); } switch (currentDataType) { 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_SPECIAL: 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; default: log.info("Unknown datatype: {}", currentDataType); // End of object, and anything else lets just skip coreType = DataTypes.CORE_SKIP; break; } return coreType; } // Basic /** * Reads a null (value) * * @return Object null */ @Override public Object readNull() { return null; } /** * Reads a boolean * * @return boolean Boolean value */ @Override public Boolean readBoolean() { return (currentDataType == AMF3.TYPE_BOOLEAN_TRUE) ? Boolean.TRUE : Boolean.FALSE; } /** * Reads a Number * * @return Number Number */ @Override public Number readNumber() { if (currentDataType == AMF3.TYPE_NUMBER) { return buf.getDouble(); } else { // we are decoding an int return readAMF3Integer(); } } /** * Reads a string * * @return String String */ @Override public String readString() { int len = readAMF3Integer(); if (len == 1) // Empty string return ""; if ((len & 1) == 0) { // Reference return stringReferences.get(len >> 1); } len >>= 1; int limit = buf.limit(); final java.nio.ByteBuffer strBuf = buf.buf(); strBuf.limit(strBuf.position() + len); final String string = AMF3.CHARSET.decode(strBuf).toString(); buf.limit(limit); // Reset the limit stringReferences.add(string); return string; } public String getString() { return readString(); } /** * Returns a date * * @return Date Date object */ @Override public Date readDate() { 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 */ public Object readArray(Deserializer deserializer) { int count = readAMF3Integer(); if ((count & 1) == 0) { // Reference return getReference(count >> 1); } count = (count >> 1); String key = readString(); amf3_mode += 1; Object result; if (key.equals("")) { // normal array List<Object> resultList = new ArrayList<Object>(count); storeReference(resultList); for (int i = 0; i < count; i++) { final Object value = deserializer.deserialize(this, Object.class); resultList.add(value); } result = resultList; } else { // associative array Map<Object, Object> resultMap = new HashMap<Object, Object>(); storeReference(resultMap); while (!key.equals("")) { final Object value = deserializer.deserialize(this, Object.class); resultMap.put(key, value); key = readString(); } for (int i = 0; i < count; i++) { final Object value = deserializer.deserialize(this, Object.class); resultMap.put(i, value); } result = resultMap; } amf3_mode -= 1; return result; } public Object readMap(Deserializer deserializer) { throw new RuntimeException("AMF3 doesn't support maps."); } // Object @SuppressWarnings("unchecked") public Object readObject(Deserializer deserializer) { int type = readAMF3Integer(); if ((type & 1) == 0) { // Reference return getReference(type >> 1); } type >>= 1; List<String> attributes = null; String className; Object result = null; boolean inlineClass = (type & 1) == 1; 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(); } amf3_mode += 1; Object instance = newInstance(className); Map<String, Object> properties = null; PendingObject pending = new PendingObject(); int tempRefId = storeReference(pending); switch (type & 0x03) { case AMF3.TYPE_OBJECT_PROPERTY: // Load object properties into map int count = type >> 2; properties = new ObjectMap<String, Object>(); if (attributes == null) { attributes = new ArrayList<String>(count); for (int i = 0; i < count; i++) { attributes.add(readString()); } classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_PROPERTY, attributes)); } 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: // Use custom class to deserialize the object if ("".equals(className)) throw new RuntimeException("need a classname to load an externalizable object"); result = newInstance(className); if (result == null) throw new RuntimeException("could not instantiate class"); if (!(result instanceof IExternalizable)) throw new RuntimeException("the class must implement the IExternalizable interface"); 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: // Load object properties into map classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_VALUE, null)); properties = new ObjectMap<String, Object>(); attributes = new LinkedList<String>(); String key = readString(); while (!"".equals(key)) { attributes.add(key); Object value = deserializer.deserialize(this, getPropertyType(instance, key)); properties.put(key, value); key = readString(); } break; default: case AMF3.TYPE_OBJECT_PROXY: if ("".equals(className)) throw new RuntimeException("need a classname to load an externalizable object"); result = newInstance(className); if (result == null) throw new RuntimeException("could not instantiate class"); if (!(result instanceof IExternalizable)) throw new RuntimeException("the class must implement the IExternalizable interface"); 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<String, Object> 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<String, Object> entry : properties.entrySet()) { // Resolve circular references final String key = entry.getKey(); Object value = entry.getValue(); if (value == pending) { value = result; } if (value instanceof PendingObject) { // Deferr setting of value until real object is created ((PendingObject) value).addPendingProperty(result, resultClass, key); continue; } try { try { final Field field = resultClass.getField(key); final Class fieldType = field.getType(); if (!fieldType.isAssignableFrom(value.getClass())) { value = ConversionUtils.convert(value, fieldType); } field.set(result, value); } catch (Exception e) { BeanUtils.setProperty(result, key, value); } } catch (Exception e) { log.error("Error mapping property: {} ({})", key, value); } } } // else fall through } } return result; } public ByteArray readByteArray() { 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; } // Others /** * Reads Custom * * @return Object Custom type object */ @Override public Object readCustom() { // Return null for now return null; } /** {@inheritDoc} */ public Object readReference() { 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 <a href="http://osflash.org/amf3/parsing_integers">parsing AMF3 * integers (external)</a> */ 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; /* Check if the integer should be negative */ if ((result & 0x10000000) != 0) { /* and extend the sign bit */ result |= 0xe0000000; } } return result; } /** {@inheritDoc} */ protected Object newInstance(String className) { log.debug("newInstance {}", className); if (className.startsWith("flex.")) { // Use Red5 compatibility class instead className = "org.red5.compatibility." + className; } return super.newInstance(className); } /** {@inheritDoc} */ public Document readXML() { 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 java.nio.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; } }