// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
// the License for the specific language governing rights and
// limitations under the License.
//
// The Original Code is RabbitMQ.
//
// The Initial Developer of the Original Code is VMware, Inc.
// Copyright (c) 2007-2012 VMware, Inc. All rights reserved.
//
package com.rabbitmq.client.impl;
import java.io.DataInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import com.rabbitmq.client.LongString;
import com.rabbitmq.client.MalformedFrameException;
/**
* Helper class to read AMQP wire-protocol encoded values.
*/
public class ValueReader
{
private static final long INT_MASK = 0xffffffff;
/**
* Protected API - Cast an int to a long without extending the
* sign bit of the int out into the high half of the long.
*/
private static final long unsignedExtend(int value)
{
long extended = value;
return extended & INT_MASK;
}
/** The stream we are reading from. */
private final DataInputStream in;
/**
* Construct a MethodArgumentReader streaming over the given DataInputStream.
*/
public ValueReader(DataInputStream in)
{
this.in = in;
}
/** Convenience method - reads a short string from a DataInput
* Stream.
*/
private static final String readShortstr(DataInputStream in)
throws IOException
{
byte [] b = new byte[in.readUnsignedByte()];
in.readFully(b);
return new String(b, "utf-8");
}
/** Public API - reads a short string. */
public final String readShortstr()
throws IOException
{
return readShortstr(this.in);
}
/** Convenience method - reads a 32-bit-length-prefix
* byte vector from a DataInputStream.
*/
private static final byte[] readBytes(final DataInputStream in)
throws IOException
{
final long contentLength = unsignedExtend(in.readInt());
if(contentLength < Integer.MAX_VALUE) {
final byte [] buffer = new byte[(int)contentLength];
in.readFully(buffer);
return buffer;
} else {
throw new UnsupportedOperationException
("Very long byte vectors and strings not currently supported");
}
}
/** Convenience method - reads a long string argument
* from a DataInputStream.
*/
private static final LongString readLongstr(final DataInputStream in)
throws IOException
{
return LongStringHelper.asLongString(readBytes(in));
}
/** Public API - reads a long string. */
public final LongString readLongstr()
throws IOException
{
return readLongstr(this.in);
}
/** Public API - reads a short integer. */
public final int readShort()
throws IOException
{
return in.readUnsignedShort();
}
/** Public API - reads an integer. */
public final int readLong()
throws IOException
{
return in.readInt();
}
/** Public API - reads a long integer. */
public final long readLonglong()
throws IOException
{
return in.readLong();
}
/**
* Reads a table argument from a given stream. Also
* called by {@link ContentHeaderPropertyReader}.
*/
private static final Map<String, Object> readTable(DataInputStream in)
throws IOException
{
long tableLength = unsignedExtend(in.readInt());
if (tableLength == 0) return Collections.emptyMap();
Map<String, Object> table = new HashMap<String, Object>();
DataInputStream tableIn = new DataInputStream
(new TruncatedInputStream(in, tableLength));
while(tableIn.available() > 0) {
String name = readShortstr(tableIn);
Object value = readFieldValue(tableIn);
if(!table.containsKey(name))
table.put(name, value);
}
return table;
}
private static final Object readFieldValue(DataInputStream in)
throws IOException {
Object value = null;
switch(in.readUnsignedByte()) {
case 'S':
value = readLongstr(in);
break;
case 'I':
value = in.readInt();
break;
case 'D':
int scale = in.readUnsignedByte();
byte [] unscaled = new byte[4];
in.readFully(unscaled);
value = new BigDecimal(new BigInteger(unscaled), scale);
break;
case 'T':
value = readTimestamp(in);
break;
case 'F':
value = readTable(in);
break;
case 'A':
value = readArray(in);
break;
case 'b':
value = in.readByte();
break;
case 'd':
value = in.readDouble();
break;
case 'f':
value = in.readFloat();
break;
case 'l':
value = in.readLong();
break;
case 's':
value = in.readShort();
break;
case 't':
value = in.readBoolean();
break;
case 'x':
value = readBytes(in);
break;
case 'V':
value = null;
break;
default:
throw new MalformedFrameException
("Unrecognised type in table");
}
return value;
}
/** Read a field-array */
private static final List<Object> readArray(DataInputStream in)
throws IOException
{
long length = unsignedExtend(in.readInt());
DataInputStream arrayIn = new DataInputStream
(new TruncatedInputStream(in, length));
List<Object> array = new ArrayList<Object>();
while(arrayIn.available() > 0) {
Object value = readFieldValue(arrayIn);
array.add(value);
}
return array;
}
/** Public API - reads a table. */
public final Map<String, Object> readTable()
throws IOException
{
return readTable(this.in);
}
/** Public API - reads an octet. */
public final int readOctet()
throws IOException
{
return in.readUnsignedByte();
}
/** Convenience method - reads a timestamp argument from the DataInputStream. */
private static final Date readTimestamp(DataInputStream in)
throws IOException
{
return new Date(in.readLong()*1000);
}
/** Public API - reads an timestamp. */
public final Date readTimestamp()
throws IOException
{
return readTimestamp(this.in);
}
}