/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services 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.
*
* Granite Data Services 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.messaging.amf.io;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.UTFDataFormatException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.Map;
import org.granite.config.ConvertersConfig;
import org.granite.config.ExternalizersConfig;
import org.granite.config.flex.ChannelConfig;
import org.granite.context.GraniteContext;
import org.granite.messaging.amf.io.convert.Converters;
import org.granite.messaging.amf.io.util.ClassGetter;
import org.granite.messaging.amf.io.util.DefaultJavaClassDescriptor;
import org.granite.messaging.amf.io.util.IndexedJavaClassDescriptor;
import org.granite.messaging.amf.io.util.JavaClassDescriptor;
import org.granite.messaging.amf.io.util.Property;
import org.granite.messaging.amf.io.util.externalizer.Externalizer;
import org.granite.messaging.amf.types.AMFDictionaryValue;
import org.granite.messaging.amf.types.AMFSpecialValue;
import org.granite.messaging.amf.types.AMFVectorIntValue;
import org.granite.messaging.amf.types.AMFVectorNumberValue;
import org.granite.messaging.amf.types.AMFVectorObjectValue;
import org.granite.messaging.amf.types.AMFVectorUintValue;
import org.granite.util.ObjectIndexedCache;
import org.granite.util.StringIndexedCache;
import org.granite.util.TypeUtil;
import org.granite.util.XMLUtil;
import org.granite.util.XMLUtilFactory;
import org.w3c.dom.Document;
import flex.messaging.io.ArrayCollection;
/**
* @author Franck WOLFF
*/
public class AMF3Serializer implements ObjectOutput, AMF3Constants {
///////////////////////////////////////////////////////////////////////////
// Static AMF3 writers.
protected static final AMF3Writer AMF3_STRING_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3String(o.toString());
}
};
protected static final AMF3Writer AMF3_BOOLEAN_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Boolean(((Boolean)o).booleanValue());
}
};
protected static final AMF3Writer AMF3_INTEGER_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Integer(((Number)o).intValue());
}
};
protected static final AMF3Writer AMF3_NUMBER_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Number(((Number)o).doubleValue());
}
};
protected static final AMF3Writer AMF3_DATE_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Date((Date)o);
}
};
protected static final AMF3Writer AMF3_CALENDAR_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Date(((Calendar)o).getTime());
}
};
protected static final AMF3Writer AMF3_DOCUMENT_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Xml((Document)o);
}
};
protected static final AMF3Writer AMF3_COLLECTION_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Collection((Collection<?>)o);
}
};
protected static final AMF3Writer AMF3_BOOLEAN_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3BooleanArray((boolean[])o);
}
};
protected static final AMF3Writer AMF3_CHAR_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3CharArray((char[])o);
}
};
protected static final AMF3Writer AMF3_CHAR_OBJECT_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3CharObjectArray((Character[])o);
}
};
protected static final AMF3Writer AMF3_BYTE_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3ByteArray((byte[])o);
}
};
protected static final AMF3Writer AMF3_BYTE_OBJECT_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3ByteObjectArray((Byte[])o);
}
};
protected static final AMF3Writer AMF3_SHORT_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3ShortArray((short[])o);
}
};
protected static final AMF3Writer AMF3_INT_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3IntArray((int[])o);
}
};
protected static final AMF3Writer AMF3_LONG_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3LongArray((long[])o);
}
};
protected static final AMF3Writer AMF3_FLOAT_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3FloatArray((float[])o);
}
};
protected static final AMF3Writer AMF3_DOUBLE_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3DoubleArray((double[])o);
}
};
protected static final AMF3Writer AMF3_OBJECT_ARRAY_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3ObjectArray(o);
}
};
protected static final AMF3Writer AMF3_SPECIAL_VALUE_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3SpecialValue((AMFSpecialValue<?>)o);
}
};
protected static final AMF3Writer AMF3_OBJECT_WRITER = new AMF3Writer() {
@Override
public void write(AMF3Serializer serializer, Object o) throws IOException {
serializer.writeAMF3Object(o);
}
};
///////////////////////////////////////////////////////////////////////////
// Instance fields.
protected final OutputStream out;
protected final byte[] buffer;
protected int position;
protected final StringIndexedCache storedStrings;
protected final ObjectIndexedCache storedObjects;
protected final Map<Class<?>, IndexedJavaClassDescriptor> storedClassDescriptors;
protected final Map<Class<?>, AMF3Writer> writersCache;
protected final Converters converters;
protected final ClassGetter classGetter;
protected final XMLUtil xmlUtil;
protected final ExternalizersConfig externalizersConfig;
protected final boolean externalizeLong;
protected final boolean externalizeBigInteger;
protected final boolean externalizeBigDecimal;
protected final boolean legacyXmlSerialization;
protected final boolean legacyCollectionSerialization;
///////////////////////////////////////////////////////////////////////////
// Constructors.
public AMF3Serializer(OutputStream out) {
this(out, 1024);
}
public AMF3Serializer(OutputStream out, int capacity) {
this.out = out;
this.buffer = new byte[capacity];
this.position = 0;
this.storedStrings = new StringIndexedCache();
this.storedObjects = new ObjectIndexedCache();
this.storedClassDescriptors = new IdentityHashMap<Class<?>, IndexedJavaClassDescriptor>();
this.writersCache = new IdentityHashMap<Class<?>, AMF3Writer>(64);
GraniteContext context = GraniteContext.getCurrentInstance();
ConvertersConfig convertersConfig = (ConvertersConfig)context.getGraniteConfig();
this.converters = convertersConfig.getConverters();
this.classGetter = convertersConfig.getClassGetter();
this.xmlUtil = XMLUtilFactory.getXMLUtil();
this.externalizersConfig = (ExternalizersConfig)context.getGraniteConfig();
this.externalizeLong = (externalizersConfig.getExternalizer(Long.class.getName()) != null);
this.externalizeBigInteger = (externalizersConfig.getExternalizer(BigInteger.class.getName()) != null);
this.externalizeBigDecimal = (externalizersConfig.getExternalizer(BigDecimal.class.getName()) != null);
String channelId = context.getAMFContext().getChannelId();
ChannelConfig channelConfig = context.getServicesConfig();
this.legacyXmlSerialization = getChannelProperty(channelId, channelConfig, "legacyXmlSerialization");
this.legacyCollectionSerialization = getChannelProperty(channelId, channelConfig, "legacyCollectionSerialization");
}
protected static boolean getChannelProperty(String channelId, ChannelConfig channelConfig, String name) {
if (channelId != null && channelConfig != null)
return channelConfig.getChannelProperty(channelId, name);
return false;
}
public void reset() {
this.storedStrings.clear();
this.storedObjects.clear();
this.storedClassDescriptors.clear();
}
///////////////////////////////////////////////////////////////////////////
// AMF3 entry point: ObjectOutput.writeObject(...) implementation.
@Override
public void writeObject(Object o) throws IOException {
if (o != null)
o = converters.revert(o);
if (o == null)
writeAMF3Null();
else {
Class<?> cls = o.getClass();
if (cls == String.class) {
writeAMF3String((String)o);
return;
}
if (cls == Integer.class) {
writeAMF3Integer(((Integer)o).intValue());
return;
}
if (cls == Boolean.class) {
writeAMF3Boolean(((Boolean)o).booleanValue());
return;
}
AMF3Writer writer = writersCache.get(cls);
if (writer == null) {
writer = getWriter(cls);
writersCache.put(cls, writer);
}
writer.write(this, o);
}
}
///////////////////////////////////////////////////////////////////////////
// AMF3 serialization methods.
protected void writeAMF3Null() throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_NULL;
}
protected void writeAMF3Boolean(boolean value) throws IOException {
ensureCapacity(1);
buffer[position++] = (value ? AMF3_BOOLEAN_TRUE : AMF3_BOOLEAN_FALSE);
}
protected void writeAMF3Integer(int i) throws IOException {
if (i < AMF3_INTEGER_MIN || i > AMF3_INTEGER_MAX)
writeAMF3Number(i);
else {
ensureCapacity(1);
buffer[position++] = AMF3_INTEGER;
writeAMF3UnsignedIntegerData(i & AMF3_SIGNED_INTEGER_MASK);
}
}
protected void writeAMF3UnsignedIntegerData(int i) throws IOException {
if (i < 0)
throw new AMF3SerializationException("Negative unsigned int: " + i);
ensureCapacity(4);
final byte[] buffer = this.buffer;
int position = this.position;
if (i < 0x4000) {
if (i < 0x80)
buffer[position++] = (byte)i;
else {
buffer[position++] = (byte)((i >>> 7) | 0x80);
buffer[position++] = (byte)(i & 0x7F);
}
}
else if (i < 0x40000000) {
if (i < 0x200000) {
buffer[position++] = (byte)((i >>> 14) | 0x80);
buffer[position++] = (byte)((i >>> 7) | 0x80);
buffer[position++] = (byte)(i & 0x7F);
}
else {
buffer[position++] = (byte)((i >>> 22) | 0x80);
buffer[position++] = (byte)((i >>> 15) | 0x80);
buffer[position++] = (byte)((i >>> 8) | 0x80);
buffer[position++] = (byte)i;
}
}
else
throw new AMF3SerializationException("Unsigned int out of range: " + i);
this.position = position;
}
protected void writeAMF3Number(double d) throws IOException {
ensureCapacity(9);
buffer[position++] = AMF3_NUMBER;
position = writeLongData(buffer, position, Double.doubleToLongBits(d));
}
protected void writeAMF3String(String s) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_STRING;
writeAMF3StringData(s);
}
protected void writeAMF3StringData(String s) throws IOException {
final int length = s.length();
if (length == 0) {
ensureCapacity(1);
buffer[position++] = 0x01;
return;
}
int index = storedStrings.putIfAbsent(s);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
final int count = utfByteCount(s);
writeAMF3UnsignedIntegerData((count << 1) | 0x01);
final byte[] buffer = this.buffer;
final int bufferLength = buffer.length;
int position = this.position;
// String chars are in [0x0000, 0x007F]: write them directly as bytes.
if (count == length) {
if (length <= bufferLength - position) {
for (int i = 0; i < length; i++)
buffer[position++] = (byte)s.charAt(i);
this.position = position;
}
else {
int i = 0;
while (position < bufferLength)
buffer[position++] = (byte)s.charAt(i++);
this.position = position;
do {
flushBuffer();
position = 0;
int max = Math.min(bufferLength, length - i);
while (position < max)
buffer[position++] = (byte)s.charAt(i++);
this.position = position;
}
while (i < length);
}
}
// We have at least one char > 0x007F but enough buffer to write them all.
else if (count <= bufferLength - position) {
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c <= 0x007F)
buffer[position++] = (byte)c;
else if (c > 0x07FF) {
buffer[position++] = (byte)(0xE0 | (c >>> 12));
buffer[position++] = (byte)(0x80 | ((c >>> 6) & 0x3F));
buffer[position++] = (byte)(0x80 | (c & 0x3F));
}
else {
buffer[position++] = (byte)(0xC0 | ((c >>> 6) & 0x1F));
buffer[position++] = (byte)(0x80 | (c & 0x3F));
}
}
this.position = position;
}
// We have at least one char > 0x007F and not enough buffer to write them all.
else {
final int bufferLengthMinus3 = buffer.length - 3;
int i = 0, total = 0;
do {
flushBuffer();
position = 0;
final int max = Math.min(count - total, bufferLengthMinus3);
while (position < max) {
char c = s.charAt(i++);
if (c <= 0x007F)
buffer[position++] = (byte)c;
else if (c > 0x07FF) {
buffer[position++] = (byte)(0xE0 | (c >>> 12));
buffer[position++] = (byte)(0x80 | ((c >>> 6) & 0x3F));
buffer[position++] = (byte)(0x80 | (c & 0x3F));
}
else {
buffer[position++] = (byte)(0xC0 | ((c >>> 6) & 0x1F));
buffer[position++] = (byte)(0x80 | (c & 0x3F));
}
}
total += position;
this.position = position;
}
while (total < count);
}
}
}
protected void writeAMF3Xml(Document doc) throws IOException {
ensureCapacity(1);
buffer[position++] = (legacyXmlSerialization ? AMF3_XML : AMF3_XMLSTRING);
int index = storedObjects.putIfAbsent(doc);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
byte[] bytes = xmlUtil.toString(doc).getBytes("UTF-8");
writeAMF3UnsignedIntegerData((bytes.length << 1) | 0x01);
flushBuffer();
out.write(bytes, 0, bytes.length);
}
}
protected void writeAMF3Date(Date date) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_DATE;
int index = storedObjects.putIfAbsent(date);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(0x01);
ensureCapacity(8);
position = writeLongData(buffer, position, Double.doubleToRawLongBits(date.getTime()));
}
}
protected void writeAMF3BooleanArray(boolean[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(array.length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (boolean i : array)
writeAMF3Boolean(i);
}
}
protected void writeAMF3CharArray(char[] array) throws IOException {
writeAMF3String(new String(array));
}
protected void writeAMF3CharObjectArray(Character[] array) throws IOException {
final int length = array.length;
char[] chars = new char[length];
for (int i = 0; i < length; i++) {
Character c = array[i];
if (c == null)
chars[i] = 0;
else
chars[i] = c.charValue();
}
writeAMF3String(new String(chars));
}
protected void writeAMF3ByteArray(byte[] bytes) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_BYTEARRAY;
int index = storedObjects.putIfAbsent(bytes);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(bytes.length << 1 | 0x01);
flushBuffer();
out.write(bytes, 0, bytes.length);
}
}
protected void writeAMF3ByteObjectArray(Byte[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_BYTEARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
final int length = array.length;
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) {
Byte b = array[i];
if (b == null)
bytes[i] = 0;
else
bytes[i] = b.byteValue();
}
flushBuffer();
out.write(bytes, 0, length);
}
}
protected void writeAMF3ShortArray(short[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(array.length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (short i : array)
writeAMF3Integer(i);
}
}
protected void writeAMF3IntArray(int[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(array.length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (int i : array)
writeAMF3Integer(i);
}
}
protected void writeAMF3LongArray(long[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(array.length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (long i : array)
writeAMF3Number(i);
}
}
protected void writeAMF3FloatArray(float[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(array.length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (float i : array)
writeAMF3Number(i);
}
}
protected void writeAMF3DoubleArray(double[] array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAMF3UnsignedIntegerData(array.length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (double i : array)
writeAMF3Number(i);
}
}
protected void writeAMF3ObjectArray(Object array) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
int index = storedObjects.putIfAbsent(array);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
int length = Array.getLength(array);
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (int i = 0; i < length; i++)
writeObject(Array.get(array, i));
}
}
protected void writeAMF3Collection(Collection<?> c) throws IOException {
if (legacyCollectionSerialization)
writeAMF3ObjectArray(c.toArray());
else {
ensureCapacity(1);
buffer[position++] = AMF3_OBJECT;
int index = storedObjects.putIfAbsent(c);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
writeAndGetAMF3Descriptor(ArrayCollection.class);
ensureCapacity(1);
buffer[position++] = AMF3_ARRAY;
// Add an arbitrary object in the dictionary instead of the
// array obtained via c.toArray(): c.toArray() must return a
// new instance each time it is called, there is no way to
// find the same instance later...
storedObjects.putIfAbsent(new Object());
writeAMF3UnsignedIntegerData(c.size() << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = 0x01;
for (Object o : c)
writeObject(o);
}
}
}
protected void writeAMF3Object(Object o) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_OBJECT;
int index = storedObjects.putIfAbsent(o);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
Class<?> oClass = classGetter.getClass(o);
JavaClassDescriptor desc = writeAndGetAMF3Descriptor(oClass);
if (desc.isExternalizable()) {
Externalizer externalizer = desc.getExternalizer();
if (externalizer != null) {
try {
externalizer.writeExternal(o, this);
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new RuntimeException("Could not externalize object: " + o, e);
}
}
else
((Externalizable)o).writeExternal(this);
}
else {
final int count = desc.getPropertiesCount();
for (int i = 0; i < count; i++) {
Property property = desc.getProperty(i);
writeObject(property.getValue(o));
}
if (desc.isDynamic()) {
Map<?, ?> oMap = (Map<?, ?>)o;
for (Map.Entry<?, ?> entry : oMap.entrySet()) {
Object key = entry.getKey();
if (key != null) {
String propertyName = key.toString();
if (propertyName.length() > 0) {
writeAMF3StringData(propertyName);
writeObject(entry.getValue());
}
}
}
ensureCapacity(1);
buffer[position++] = 0x01;
}
}
}
}
protected void writeAMF3SpecialValue(AMFSpecialValue<?> value) throws IOException {
switch (value.type) {
case AMF3_DICTIONARY:
writeAMF3Dictionary((AMFDictionaryValue)value);
break;
case AMF3_VECTOR_INT:
writeAMF3VectorInt((AMFVectorIntValue)value);
break;
case AMF3_VECTOR_NUMBER:
writeAMF3VectorNumber((AMFVectorNumberValue)value);
break;
case AMF3_VECTOR_UINT:
writeAMF3VectorUint((AMFVectorUintValue)value);
break;
case AMF3_VECTOR_OBJECT:
writeAMF3VectorObject((AMFVectorObjectValue)value);
break;
default:
throw new RuntimeException("Unsupported AMF special value: " + value);
}
}
protected void writeAMF3VectorObject(AMFVectorObjectValue value) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_VECTOR_OBJECT;
Object o = value.value;
int index = storedObjects.putIfAbsent(o);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
int length = getArrayOrCollectionLength(o);
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = (byte)(value.fixed ? 0x01 : 0x00);
writeAMF3StringData(value.type);
if (o.getClass().isArray()) {
for (int i = 0; i < length; i++)
writeObject(Array.get(o, i));
}
else {
for (Object item : (Collection<?>)o)
writeObject(item);
}
}
}
protected void writeAMF3VectorInt(AMFVectorIntValue value) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_VECTOR_INT;
Object o = value.value;
int index = storedObjects.putIfAbsent(o);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
int length = getArrayOrCollectionLength(o);
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = (byte)(value.fixed ? 0x01 : 0x00);
if (o.getClass().isArray()) {
for (int i = 0; i < length; i++) {
ensureCapacity(4);
position = writeIntData(buffer, position, ((Number)Array.get(o, i)).intValue());
}
}
else {
for (Object item : (Collection<?>)o) {
ensureCapacity(4);
position = writeIntData(buffer, position, ((Number)item).intValue());
}
}
}
}
protected void writeAMF3VectorNumber(AMFVectorNumberValue value) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_VECTOR_NUMBER;
Object o = value.value;
int index = storedObjects.putIfAbsent(o);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
int length = getArrayOrCollectionLength(o);
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = (byte)(value.fixed ? 0x01 : 0x00);
if (o.getClass().isArray()) {
for (int i = 0; i < length; i++) {
ensureCapacity(8);
position = writeLongData(buffer, position, Double.doubleToLongBits(((Number)Array.get(o, i)).doubleValue()));
}
}
else {
for (Object item : (Collection<?>)o) {
ensureCapacity(8);
position = writeLongData(buffer, position, Double.doubleToLongBits(((Number)item).doubleValue()));
}
}
}
}
protected void writeAMF3VectorUint(AMFVectorUintValue value) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_VECTOR_UINT;
Object o = value.value;
int index = storedObjects.putIfAbsent(o);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
int length = getArrayOrCollectionLength(o);
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = (byte)(value.fixed ? 0x01 : 0x00);
if (o.getClass().isArray()) {
for (int i = 0; i < length; i++) {
ensureCapacity(4);
position = writeIntData(buffer, position, ((Number)Array.get(o, i)).intValue());
}
}
else {
for (Object item : (Collection<?>)o) {
ensureCapacity(4);
position = writeIntData(buffer, position, ((Number)item).intValue());
}
}
}
}
protected void writeAMF3Dictionary(AMFDictionaryValue value) throws IOException {
ensureCapacity(1);
buffer[position++] = AMF3_DICTIONARY;
Map<?, ?> o = value.value;
int index = storedObjects.putIfAbsent(o);
if (index >= 0)
writeAMF3UnsignedIntegerData(index << 1);
else {
int length = o.size();
writeAMF3UnsignedIntegerData(length << 1 | 0x01);
ensureCapacity(1);
buffer[position++] = (byte)(value.weakKeys ? 0x01 : 0x00);
for (Map.Entry<?, ?> entry : o.entrySet()) {
writeObject(entry.getKey());
writeObject(entry.getValue());
}
}
}
///////////////////////////////////////////////////////////////////////////
// Utilities.
protected int getArrayOrCollectionLength(Object o) {
if (o.getClass().isArray())
return Array.getLength(o);
return ((Collection<?>)o).size();
}
protected AMF3Writer getWriter(Class<?> cls) {
if (cls.isPrimitive())
throw new RuntimeException("Illegal primitive class: " + cls);
if (Externalizable.class.isAssignableFrom(cls))
return AMF3_OBJECT_WRITER;
if (cls == String.class || cls == Character.class)
return AMF3_STRING_WRITER;
if (Number.class.isAssignableFrom(cls)) {
if (cls == Integer.class || cls == Short.class || cls == Byte.class)
return AMF3_INTEGER_WRITER;
if (externalizeLong && cls == Long.class)
return AMF3_OBJECT_WRITER;
if (externalizeBigInteger && BigInteger.class.isAssignableFrom(cls))
return AMF3_OBJECT_WRITER;
if (externalizeBigDecimal && BigDecimal.class.isAssignableFrom(cls))
return AMF3_OBJECT_WRITER;
return AMF3_NUMBER_WRITER;
}
if (Collection.class.isAssignableFrom(cls))
return AMF3_COLLECTION_WRITER;
if (cls.isArray()) {
Class<?> componentType = cls.getComponentType();
if (componentType == boolean.class)
return AMF3_BOOLEAN_ARRAY_WRITER;
if (componentType == char.class)
return AMF3_CHAR_ARRAY_WRITER;
if (componentType == Character.class)
return AMF3_CHAR_OBJECT_ARRAY_WRITER;
if (componentType == byte.class)
return AMF3_BYTE_ARRAY_WRITER;
if (componentType == Byte.class)
return AMF3_BYTE_OBJECT_ARRAY_WRITER;
if (componentType == short.class)
return AMF3_SHORT_ARRAY_WRITER;
if (componentType == int.class)
return AMF3_INT_ARRAY_WRITER;
if (componentType == long.class)
return AMF3_LONG_ARRAY_WRITER;
if (componentType == float.class)
return AMF3_FLOAT_ARRAY_WRITER;
if (componentType == double.class)
return AMF3_DOUBLE_ARRAY_WRITER;
return AMF3_OBJECT_ARRAY_WRITER;
}
if (cls == Boolean.class)
return AMF3_BOOLEAN_WRITER;
if (Date.class.isAssignableFrom(cls))
return AMF3_DATE_WRITER;
if (Calendar.class.isAssignableFrom(cls))
return AMF3_CALENDAR_WRITER;
if (Document.class.isAssignableFrom(cls))
return AMF3_DOCUMENT_WRITER;
if (AMFSpecialValue.class.isAssignableFrom(cls))
return AMF3_SPECIAL_VALUE_WRITER;
return AMF3_OBJECT_WRITER;
}
protected JavaClassDescriptor writeAndGetAMF3Descriptor(Class<?> cls) throws IOException {
JavaClassDescriptor desc = null;
IndexedJavaClassDescriptor iDesc = storedClassDescriptors.get(cls);
if (iDesc != null) {
desc = iDesc.getDescriptor();
writeAMF3UnsignedIntegerData(iDesc.getIndex() << 2 | 0x01);
}
else {
iDesc = addToStoredClassDescriptors(cls);
desc = iDesc.getDescriptor();
final int count = desc.getPropertiesCount();
writeAMF3UnsignedIntegerData((count << 4) | (desc.getEncoding() << 2) | 0x03);
writeAMF3StringData(desc.getName());
for (int i = 0; i < count; i++)
writeAMF3StringData(desc.getPropertyName(i));
}
return desc;
}
protected IndexedJavaClassDescriptor addToStoredClassDescriptors(Class<?> clazz) {
JavaClassDescriptor desc = externalizersConfig.getJavaDescriptorsCache().get(clazz.getName());
if (desc == null) {
// find custom class descriptor and instantiate it if any.
Class<? extends JavaClassDescriptor> descriptorType = externalizersConfig.getJavaDescriptor(clazz.getName());
if (descriptorType != null) {
try {
desc = TypeUtil.newInstance(descriptorType, new Class[]{Class.class}, new Object[]{clazz});
}
catch (Exception e) {
throw new RuntimeException("Could not instantiate Java descriptor: " + descriptorType);
}
}
else
desc = new DefaultJavaClassDescriptor(clazz);
externalizersConfig.getJavaDescriptorsCache().putIfAbsent(clazz.getName(), desc);
}
IndexedJavaClassDescriptor iDesc = new IndexedJavaClassDescriptor(storedClassDescriptors.size(), desc);
storedClassDescriptors.put(clazz, iDesc);
return iDesc;
}
protected void ensureCapacity(int capacity) throws IOException {
if (buffer.length - position < capacity)
flushBuffer();
}
protected void flushBuffer() throws IOException {
if (position > 0) {
out.write(buffer, 0, position);
position = 0;
}
}
protected static int utfByteCount(String s) {
final int length = s.length();
int count = length;
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c > 0x007F) {
if (c > 0x07FF)
count += 2;
else
count++;
}
}
return count;
}
protected static int writeIntData(byte[] buffer, int position, int i) {
buffer[position++] = (byte)(i >>> 24);
buffer[position++] = (byte)(i >>> 16);
buffer[position++] = (byte)(i >>> 8);
buffer[position++] = (byte)i;
return position;
}
protected static int writeLongData(byte[] buffer, int position, long l) {
buffer[position++] = (byte)(l >>> 56);
buffer[position++] = (byte)(l >>> 48);
buffer[position++] = (byte)(l >>> 40);
buffer[position++] = (byte)(l >>> 32);
buffer[position++] = (byte)(l >>> 24);
buffer[position++] = (byte)(l >>> 16);
buffer[position++] = (byte)(l >>> 8);
buffer[position++] = (byte)l;
return position;
}
///////////////////////////////////////////////////////////////////////////
// ObjectOutput implementation (except writeObject): not optimized as these
// methods are very unlikely used with AMF3...
@Override
public void writeBoolean(boolean v) throws IOException {
flushBuffer();
out.write(v ? 1 : 0);
}
@Override
public void writeByte(int v) throws IOException {
flushBuffer();
out.write(v);
}
@Override
public void writeShort(int v) throws IOException {
flushBuffer();
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
}
@Override
public void writeChar(int v) throws IOException {
flushBuffer();
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
}
@Override
public void writeInt(int v) throws IOException {
flushBuffer();
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
}
@Override
public void writeLong(long v) throws IOException {
flushBuffer();
byte writeBuffer[] = new byte[8];
writeBuffer[0] = (byte)(v >>> 56);
writeBuffer[1] = (byte)(v >>> 48);
writeBuffer[2] = (byte)(v >>> 40);
writeBuffer[3] = (byte)(v >>> 32);
writeBuffer[4] = (byte)(v >>> 24);
writeBuffer[5] = (byte)(v >>> 16);
writeBuffer[6] = (byte)(v >>> 8);
writeBuffer[7] = (byte)(v >>> 0);
out.write(writeBuffer, 0, 8);
}
@Override
public void writeFloat(float v) throws IOException {
flushBuffer();
writeInt(Float.floatToIntBits(v));
}
@Override
public void writeDouble(double v) throws IOException {
flushBuffer();
writeLong(Double.doubleToLongBits(v));
}
@Override
public void writeBytes(String s) throws IOException {
flushBuffer();
final int len = s.length();
for (int i = 0 ; i < len ; i++)
out.write((byte)s.charAt(i));
}
@Override
public void writeChars(String s) throws IOException {
flushBuffer();
int len = s.length();
for (int i = 0 ; i < len ; i++) {
int v = s.charAt(i);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
}
}
@Override
public void writeUTF(String s) throws IOException {
flushBuffer();
final int strlen = s.length();
int utflen = 0;
int c, count = 0;
for (int i = 0; i < strlen; i++) {
c = s.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
if (utflen > 65535)
throw new UTFDataFormatException("encoded string too long: " + utflen + " bytes");
byte[] bytearr = new byte[utflen+2];
bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
int i=0;
for (i=0; i<strlen; i++) {
c = s.charAt(i);
if (!((c >= 0x0001) && (c <= 0x007F)))
break;
bytearr[count++] = (byte) c;
}
for (;i < strlen; i++){
c = s.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
bytearr[count++] = (byte) c;
} else if (c > 0x07FF) {
bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
} else {
bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
}
}
out.write(bytearr, 0, utflen+2);
}
@Override
public void write(int b) throws IOException {
flushBuffer();
out.write(b);
}
@Override
public void write(byte[] b) throws IOException {
flushBuffer();
out.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
flushBuffer();
out.write(b, off, len);
}
@Override
public void flush() throws IOException {
flushBuffer();
out.flush();
}
@Override
public void close() throws IOException {
flushBuffer();
out.close();
}
///////////////////////////////////////////////////////////////////////////
// General interface for specific AMF3 writers (see static writers above).
protected interface AMF3Writer {
public void write(AMF3Serializer serializer, Object o) throws IOException;
}
}