blob: cd7b4d4d74eedc0bb9e7c71e61a18d669f5a1f93 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.qpid.transport.codec;
import static org.apache.qpid.transport.util.Functions.lsb;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.qpid.transport.Range;
import org.apache.qpid.transport.RangeSet;
import org.apache.qpid.transport.Struct;
import org.apache.qpid.transport.Type;
import org.apache.qpid.transport.Xid;
/**
* AbstractEncoder
*
* @author Rafael H. Schloming
*/
public abstract class AbstractEncoder implements Encoder
{
private static Map<Class<?>,Type> ENCODINGS = new HashMap<Class<?>,Type>();
static
{
ENCODINGS.put(Boolean.class, Type.BOOLEAN);
ENCODINGS.put(String.class, Type.STR16);
ENCODINGS.put(Long.class, Type.INT64);
ENCODINGS.put(Integer.class, Type.INT32);
ENCODINGS.put(Short.class, Type.INT16);
ENCODINGS.put(Byte.class, Type.INT8);
ENCODINGS.put(Map.class, Type.MAP);
ENCODINGS.put(List.class, Type.LIST);
ENCODINGS.put(Float.class, Type.FLOAT);
ENCODINGS.put(Double.class, Type.DOUBLE);
ENCODINGS.put(Character.class, Type.CHAR);
ENCODINGS.put(byte[].class, Type.VBIN32);
ENCODINGS.put(UUID.class, Type.UUID);
ENCODINGS.put(Xid.class, Type.STRUCT32);
}
private final Map<String,byte[]> str8cache = new LinkedHashMap<String,byte[]>()
{
@Override protected boolean removeEldestEntry(Map.Entry<String,byte[]> me)
{
return size() > 4*1024;
}
};
protected abstract void doPut(byte b);
protected abstract void doPut(ByteBuffer src);
protected void put(byte b)
{
doPut(b);
}
protected void put(ByteBuffer src)
{
doPut(src);
}
protected void put(byte[] bytes)
{
put(ByteBuffer.wrap(bytes));
}
protected abstract int beginSize8();
protected abstract void endSize8(int pos);
protected abstract int beginSize16();
protected abstract void endSize16(int pos);
protected abstract int beginSize32();
protected abstract void endSize32(int pos);
public void writeUint8(short b)
{
assert b < 0x100;
put((byte) b);
}
public void writeUint16(int s)
{
assert s < 0x10000;
put(lsb(s >>> 8));
put(lsb(s));
}
public void writeUint32(long i)
{
assert i < 0x100000000L;
put(lsb(i >>> 24));
put(lsb(i >>> 16));
put(lsb(i >>> 8));
put(lsb(i));
}
public void writeSequenceNo(int i)
{
writeUint32(i);
}
public void writeUint64(long l)
{
for (int i = 0; i < 8; i++)
{
put(lsb(l >> (56 - i*8)));
}
}
public void writeDatetime(long l)
{
writeUint64(l);
}
public void writeStr8(String s)
{
if (s == null)
{
s = "";
}
byte[] bytes = str8cache.get(s);
if (bytes == null)
{
bytes = s.getBytes(StandardCharsets.UTF_8);
if (bytes.length > 255)
{
throw new IllegalArgumentException(String.format("String too long (%d) for str8", bytes.length));
}
str8cache.put(s, bytes);
}
writeUint8((short) bytes.length);
put(bytes);
}
public void writeStr16(String s)
{
if (s == null)
{
s = "";
}
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
if (bytes.length > 65535)
{
throw new IllegalArgumentException(String.format("String too long (%d) for str16", bytes.length));
}
writeUint16(bytes.length);
put(bytes);
}
public void writeVbin8(byte[] bytes)
{
if (bytes == null) { bytes = new byte[0]; }
if (bytes.length > 255)
{
throw new IllegalArgumentException("array too long: " + bytes.length);
}
writeUint8((short) bytes.length);
put(ByteBuffer.wrap(bytes));
}
public void writeVbin16(byte[] bytes)
{
if (bytes == null) { bytes = new byte[0]; }
writeUint16(bytes.length);
put(ByteBuffer.wrap(bytes));
}
public void writeVbin32(byte[] bytes)
{
if (bytes == null) { bytes = new byte[0]; }
writeUint32(bytes.length);
put(ByteBuffer.wrap(bytes));
}
public void writeSequenceSet(RangeSet ranges)
{
if (ranges == null)
{
writeUint16((short) 0);
}
else
{
writeUint16(ranges.size() * 8);
for (Range range : ranges)
{
writeSequenceNo(range.getLower());
writeSequenceNo(range.getUpper());
}
}
}
public void writeByteRanges(RangeSet ranges)
{
throw new Error("not implemented");
}
public void writeUuid(UUID uuid)
{
long msb = 0;
long lsb = 0;
if (uuid != null)
{
msb = uuid.getMostSignificantBits();
lsb = uuid.getLeastSignificantBits();
}
writeUint64(msb);
writeUint64(lsb);
}
public void writeStruct(int type, Struct s)
{
boolean empty = false;
if (s == null)
{
s = Struct.create(type);
empty = true;
}
int width = s.getSizeWidth();
int pos = -1;
if (width > 0)
{
pos = beginSize(width);
}
if (type > 0)
{
writeUint16(type);
}
s.write(this);
if (width > 0)
{
endSize(width, pos);
}
}
public void writeStruct32(Struct s)
{
if (s == null)
{
writeUint32(0);
}
else
{
int pos = beginSize32();
writeUint16(s.getEncodedType());
s.write(this);
endSize32(pos);
}
}
private Type encoding(Object value)
{
if (value == null)
{
return Type.VOID;
}
Class klass = value.getClass();
Type type = resolve(klass);
if (type == null)
{
throw new IllegalArgumentException
("unable to resolve type: " + klass + ", " + value);
}
else
{
return type;
}
}
static final Type resolve(Class klass)
{
Type type = ENCODINGS.get(klass);
if (type != null)
{
return type;
}
Class sup = klass.getSuperclass();
if (sup != null)
{
type = resolve(klass.getSuperclass());
if (type != null)
{
return type;
}
}
for (Class iface : klass.getInterfaces())
{
type = resolve(iface);
if (type != null)
{
return type;
}
}
return null;
}
public void writeMap(Map<String,Object> map)
{
int pos = beginSize32();
if (map != null)
{
writeUint32(map.size());
writeMapEntries(map);
}
endSize32(pos);
}
protected void writeMapEntries(Map<String,Object> map)
{
for (Map.Entry<String,Object> entry : map.entrySet())
{
String key = entry.getKey();
Object value = entry.getValue();
Type type = encoding(value);
writeStr8(key);
put(type.getCode());
write(type, value);
}
}
public void writeList(List<Object> list)
{
int pos = beginSize32();
if (list != null)
{
writeUint32(list.size());
writeListEntries(list);
}
endSize32(pos);
}
protected void writeListEntries(List<Object> list)
{
for (Object value : list)
{
Type type = encoding(value);
put(type.getCode());
write(type, value);
}
}
public void writeArray(List<Object> array)
{
int pos = beginSize32();
if (array != null)
{
writeArrayEntries(array);
}
endSize32(pos);
}
protected void writeArrayEntries(List<Object> array)
{
Type type;
if (array.isEmpty())
{
return;
}
else
{
type = encoding(array.get(0));
}
put(type.getCode());
writeUint32(array.size());
for (Object value : array)
{
write(type, value);
}
}
private void writeSize(Type t, int size)
{
if (t.isFixed())
{
if (size != t.getWidth())
{
throw new IllegalArgumentException
("size does not match fixed width " + t.getWidth() + ": " +
size);
}
}
else
{
writeSize(t.getWidth(), size);
}
}
private void writeSize(int width, int size)
{
// XXX: should check lengths
switch (width)
{
case 1:
writeUint8((short) size);
break;
case 2:
writeUint16(size);
break;
case 4:
writeUint32(size);
break;
default:
throw new IllegalStateException("illegal width: " + width);
}
}
private int beginSize(int width)
{
switch (width)
{
case 1:
return beginSize8();
case 2:
return beginSize16();
case 4:
return beginSize32();
default:
throw new IllegalStateException("illegal width: " + width);
}
}
private void endSize(int width, int pos)
{
switch (width)
{
case 1:
endSize8(pos);
break;
case 2:
endSize16(pos);
break;
case 4:
endSize32(pos);
break;
default:
throw new IllegalStateException("illegal width: " + width);
}
}
private void writeBytes(Type t, byte[] bytes)
{
writeSize(t, bytes.length);
put(bytes);
}
private <T> T coerce(Class<T> klass, Object value)
{
if (klass.isInstance(value))
{
return klass.cast(value);
}
else
{
throw new IllegalArgumentException("" + value);
}
}
private void write(Type t, Object value)
{
switch (t)
{
case BIN8:
case UINT8:
writeUint8(coerce(Short.class, value));
break;
case INT8:
put(coerce(Byte.class, value));
break;
case CHAR:
put((byte) ((char)coerce(Character.class, value)));
break;
case BOOLEAN:
if (coerce(Boolean.class, value))
{
put((byte) 1);
}
else
{
put((byte) 0);
}
break;
case BIN16:
case UINT16:
writeUint16(coerce(Integer.class, value));
break;
case INT16:
writeUint16(coerce(Short.class, value));
break;
case BIN32:
case UINT32:
writeUint32(coerce(Long.class, value));
break;
case CHAR_UTF32:
case INT32:
writeUint32(coerce(Integer.class, value));
break;
case FLOAT:
writeUint32(Float.floatToIntBits(coerce(Float.class, value)));
break;
case BIN64:
case UINT64:
case INT64:
case DATETIME:
writeUint64(coerce(Long.class, value));
break;
case DOUBLE:
long bits = Double.doubleToLongBits(coerce(Double.class, value));
writeUint64(bits);
break;
case UUID:
writeUuid(coerce(UUID.class, value));
break;
case STR8:
writeStr8(coerce(String.class, value));
break;
case STR16:
writeStr16(coerce(String.class, value));
break;
case STR8_LATIN:
case STR8_UTF16:
case STR16_LATIN:
case STR16_UTF16:
// XXX: need to do character conversion
writeBytes(t, coerce(String.class, value).getBytes());
break;
case MAP:
writeMap((Map<String,Object>) coerce(Map.class, value));
break;
case LIST:
writeList(coerce(List.class, value));
break;
case ARRAY:
writeArray(coerce(List.class, value));
break;
case STRUCT32:
writeStruct32(coerce(Struct.class, value));
break;
case BIN40:
case DEC32:
case BIN72:
case DEC64:
// XXX: what types are we supposed to use here?
writeBytes(t, coerce(byte[].class, value));
break;
case VOID:
break;
default:
writeBytes(t, coerce(byte[].class, value));
break;
}
}
}