| /* |
| * 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.proton.codec; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.UUID; |
| |
| import org.apache.qpid.proton.amqp.Symbol; |
| import org.junit.Test; |
| import org.mockito.Mockito; |
| |
| /** |
| * Test decoding of AMQP Array types |
| */ |
| public class ArrayTypeCodecTest extends CodecTestSupport { |
| |
| private final int LARGE_ARRAY_SIZE = 2048; |
| private final int SMALL_ARRAY_SIZE = 32; |
| |
| @Test |
| public void testArrayOfPrimitiveBooleanObjects() throws IOException { |
| final int size = 10; |
| |
| boolean[] source = new boolean[size]; |
| for (int i = 0; i < size; ++i) { |
| source[i] = i % 2 == 0; |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(buffer); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| assertTrue(result.getClass().getComponentType().isPrimitive()); |
| |
| boolean[] array = (boolean[]) result; |
| assertEquals(size, array.length); |
| |
| for (int i = 0; i < size; ++i) { |
| assertEquals(source[i], array[i]); |
| } |
| } |
| |
| @Test |
| public void testZeroSizedArrayOfPrimitiveBooleanObjects() throws IOException { |
| boolean[] source = new boolean[0]; |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| assertTrue(result.getClass().getComponentType().isPrimitive()); |
| |
| boolean[] array = (boolean[]) result; |
| assertEquals(source.length, array.length); |
| } |
| |
| @Test |
| public void testArrayOfBooleanObjects() throws IOException { |
| final int size = 10; |
| |
| Boolean[] source = new Boolean[size]; |
| for (int i = 0; i < size; ++i) { |
| source[i] = i % 2 == 0; |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| assertTrue(result.getClass().getComponentType().isPrimitive()); |
| |
| boolean[] array = (boolean[]) result; |
| assertEquals(size, array.length); |
| |
| for (int i = 0; i < size; ++i) { |
| assertEquals(source[i], array[i]); |
| } |
| } |
| |
| @Test |
| public void testZeroSizedArrayOfBooleanObjects() throws IOException { |
| Boolean[] source = new Boolean[0]; |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| assertTrue(result.getClass().getComponentType().isPrimitive()); |
| |
| boolean[] array = (boolean[]) result; |
| assertEquals(source.length, array.length); |
| } |
| |
| @Test |
| public void testDecodeSmallBooleanArray() throws IOException { |
| doTestDecodeBooleanArrayType(SMALL_ARRAY_SIZE); |
| } |
| |
| @Test |
| public void testDecodeLargeBooleanArray() throws IOException { |
| doTestDecodeBooleanArrayType(LARGE_ARRAY_SIZE); |
| } |
| |
| private void doTestDecodeBooleanArrayType(int size) throws IOException { |
| boolean[] source = new boolean[size]; |
| for (int i = 0; i < size; ++i) { |
| source[i] = i % 2 == 0; |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| assertTrue(result.getClass().getComponentType().isPrimitive()); |
| |
| boolean[] array = (boolean[]) result; |
| assertEquals(size, array.length); |
| |
| for (int i = 0; i < size; ++i) { |
| assertEquals(source[i], array[i]); |
| } |
| } |
| |
| @Test |
| public void testDecodeSmallSymbolArray() throws IOException { |
| doTestDecodeSymbolArrayType(SMALL_ARRAY_SIZE); |
| } |
| |
| @Test |
| public void testDecodeLargeSymbolArray() throws IOException { |
| doTestDecodeSymbolArrayType(LARGE_ARRAY_SIZE); |
| } |
| |
| private void doTestDecodeSymbolArrayType(int size) throws IOException { |
| Symbol[] source = new Symbol[size]; |
| for (int i = 0; i < size; ++i) { |
| source[i] = Symbol.valueOf("test->" + i); |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| |
| Symbol[] array = (Symbol[]) result; |
| assertEquals(size, array.length); |
| |
| for (int i = 0; i < size; ++i) { |
| assertEquals(source[i], array[i]); |
| } |
| } |
| |
| @Test |
| public void testDecodeSmallUUIDArray() throws IOException { |
| doTestDecodeUUDIArrayType(SMALL_ARRAY_SIZE); |
| } |
| |
| @Test |
| public void testDecodeLargeUUDIArray() throws IOException { |
| doTestDecodeUUDIArrayType(LARGE_ARRAY_SIZE); |
| } |
| |
| private void doTestDecodeUUDIArrayType(int size) throws IOException { |
| UUID[] source = new UUID[size]; |
| for (int i = 0; i < size; ++i) { |
| source[i] = UUID.randomUUID(); |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| |
| UUID[] array = (UUID[]) result; |
| assertEquals(size, array.length); |
| |
| for (int i = 0; i < size; ++i) { |
| assertEquals(source[i], array[i]); |
| } |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| @Test |
| public void testArrayOfListsOfUUIDs() throws IOException { |
| ArrayList<UUID>[] source = new ArrayList[2]; |
| for (int i = 0; i < source.length; ++i) { |
| source[i] = new ArrayList<UUID>(3); |
| source[i].add(UUID.randomUUID()); |
| source[i].add(UUID.randomUUID()); |
| source[i].add(UUID.randomUUID()); |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| |
| List[] list = (List[]) result; |
| assertEquals(source.length, list.length); |
| |
| for (int i = 0; i < list.length; ++i) { |
| assertEquals(source[i], list[i]); |
| } |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| @Test |
| public void testArrayOfMApsOfStringToUUIDs() throws IOException { |
| Map<String, UUID>[] source = new LinkedHashMap[2]; |
| for (int i = 0; i < source.length; ++i) { |
| source[i] = new LinkedHashMap<String, UUID>(); |
| source[i].put("1", UUID.randomUUID()); |
| source[i].put("2", UUID.randomUUID()); |
| source[i].put("3", UUID.randomUUID()); |
| } |
| |
| encoder.writeArray(source); |
| |
| buffer.clear(); |
| |
| Object result = decoder.readObject(); |
| assertNotNull(result); |
| assertTrue(result.getClass().isArray()); |
| |
| Map[] map = (Map[]) result; |
| assertEquals(source.length, map.length); |
| |
| for (int i = 0; i < map.length; ++i) { |
| assertEquals(source[i], map[i]); |
| } |
| } |
| |
| @Test |
| public void testEncodeSmallBooleanArrayReservesSpaceForPayload() throws IOException { |
| doTestEncodeBooleanArrayTypeReservation(SMALL_ARRAY_SIZE); |
| } |
| |
| @Test |
| public void testEncodeLargeBooleanArrayReservesSpaceForPayload() throws IOException { |
| doTestEncodeBooleanArrayTypeReservation(LARGE_ARRAY_SIZE); |
| } |
| |
| private void doTestEncodeBooleanArrayTypeReservation(int size) throws IOException { |
| boolean[] source = new boolean[size]; |
| for (int i = 0; i < size; ++i) { |
| source[i] = i % 2 == 0; |
| } |
| |
| WritableBuffer writable = new WritableBuffer.ByteBufferWrapper(this.buffer); |
| WritableBuffer spy = Mockito.spy(writable); |
| |
| encoder.setByteBuffer(spy); |
| encoder.writeArray(source); |
| |
| // Check that the ArrayType tries to reserve space, actual encoding size not computed here. |
| Mockito.verify(spy).ensureRemaining(Mockito.anyInt()); |
| } |
| |
| @Test |
| public void testEncodeDecodeByteArray() throws IOException { |
| int count = 100; |
| byte[] source = createPayloadArrayBytes(count); |
| |
| try { |
| assertEquals("Unexpected source array length", count, source.length); |
| |
| int encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor. |
| int arrayPayloadSize = encodingWidth + 1 + count; // variable width for element count + byte type descriptor + number of elements |
| int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for array size + other encoded payload |
| byte[] expectedEncoding = new byte[expectedEncodedArraySize]; |
| ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding); |
| |
| // Write the array encoding code, array size, and element count |
| if(count < 254) { |
| expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code |
| expectedEncodingWrapper.put((byte) arrayPayloadSize); |
| expectedEncodingWrapper.put((byte) count); |
| } else { |
| expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code |
| expectedEncodingWrapper.putInt(arrayPayloadSize); |
| expectedEncodingWrapper.putInt(count); |
| } |
| |
| // Write the type descriptor |
| expectedEncodingWrapper.put((byte) 0x51); // 'byte' type descriptor code |
| |
| // Write the elements |
| for (int i = 0; i < count; i++) { |
| expectedEncodingWrapper.put(source[i]); |
| } |
| |
| assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining()); |
| |
| // Now verify against the actual encoding of the array |
| assertEquals("Unexpected buffer position", 0, buffer.position()); |
| encoder.writeArray(source); |
| assertEquals("Unexpected encoded payload length", expectedEncodedArraySize, buffer.position()); |
| |
| byte[] actualEncoding = new byte[expectedEncodedArraySize]; |
| buffer.flip(); |
| buffer.get(actualEncoding); |
| assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining()); |
| |
| assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding); |
| |
| // Now verify against the decoding |
| buffer.flip(); |
| Object decoded = decoder.readObject(); |
| assertNotNull(decoded); |
| assertTrue(decoded.getClass().isArray()); |
| assertTrue(decoded.getClass().getComponentType().isPrimitive()); |
| assertEquals(byte.class, decoded.getClass().getComponentType()); |
| |
| assertArrayEquals("Unexpected decoding", source, (byte[]) decoded); |
| } |
| catch (Throwable t) { |
| System.err.println("Error during test, source array: " + Arrays.toString(source)); |
| throw t; |
| } |
| } |
| |
| private static byte[] createPayloadArrayBytes(int length) { |
| Random rand = new Random(System.currentTimeMillis()); |
| |
| byte[] payload = new byte[length]; |
| for (int i = 0; i < length; i++) { |
| payload[i] = (byte) (64 + 1 + rand.nextInt(9)); |
| } |
| |
| return payload; |
| } |
| } |