| /* |
| * |
| * 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; |
| } |
| } |
| |
| } |