blob: 3ba20079910407ef3c1e69e3c7bddd6c816e436a [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.proton.codec;
import static org.junit.Assert.assertEquals;
import java.lang.Character.UnicodeBlock;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.junit.Test;
/**
* Test the encoding and decoding of {@link StringType} values.
*/
public class StringTypeTest
{
private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
private static final List<String> TEST_DATA = generateTestData();
/**
* Loop over all the chars in given {@link UnicodeBlock}s and return a {@link Set <String>}
* containing all the possible values as their {@link String} values.
*
* @param blocks
* the {@link UnicodeBlock}s to loop over
* @return a {@link Set <String>} containing all the possible values as {@link String}
* values
*/
private static Set<String> getAllStringsFromUnicodeBlocks(final UnicodeBlock... blocks)
{
final Set<UnicodeBlock> blockSet = new HashSet<UnicodeBlock>(Arrays.asList(blocks));
final Set<String> strings = new HashSet<String>();
for (int codePoint = 0; codePoint <= Character.MAX_CODE_POINT; codePoint++)
{
if (blockSet.contains(UnicodeBlock.of(codePoint)))
{
final int charCount = Character.charCount(codePoint);
final StringBuilder sb = new StringBuilder(
charCount);
if (charCount == 1)
{
sb.append(String.valueOf((char) codePoint));
}
else if (charCount == 2)
{
//TODO: use Character.highSurrogate(codePoint) and Character.lowSurrogate(codePoint) when Java 7 is baseline
char highSurrogate = (char) ((codePoint >>> 10) + ('\uD800' - (0x010000 >>> 10)));
char lowSurrogate = (char) ((codePoint & 0x3ff) + '\uDC00');
sb.append(highSurrogate);
sb.append(lowSurrogate);
}
else
{
throw new IllegalArgumentException("Character.charCount of "
+ charCount + " not supported.");
}
strings.add(sb.toString());
}
}
return strings;
}
/**
* Test the encoding and decoding of various complicated Unicode characters which will end
* up as "surrogate pairs" when encoded to UTF-8
*/
@Test
public void calculateUTF8Length()
{
for (final String input : TEST_DATA)
{
assertEquals("Incorrect string length calculated for string '"+input+"'",input.getBytes(CHARSET_UTF8).length, StringType.calculateUTF8Length(input));
}
}
/**
* Test the encoding and decoding of various Unicode characters
*/
@Test
public void encodeDecodeStrings()
{
final DecoderImpl decoder = new DecoderImpl();
final EncoderImpl encoder = new EncoderImpl(decoder);
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
final ByteBuffer bb = ByteBuffer.allocate(16);
for (final String input : TEST_DATA)
{
bb.clear();
final AmqpValue inputValue = new AmqpValue(input);
encoder.setByteBuffer(bb);
encoder.writeObject(inputValue);
bb.clear();
decoder.setByteBuffer(bb);
final AmqpValue outputValue = (AmqpValue) decoder.readObject();
assertEquals("Failed to round trip String correctly: ", input, outputValue.getValue());
}
}
@Test
public void testSkipString()
{
final DecoderImpl decoder = new DecoderImpl();
final EncoderImpl encoder = new EncoderImpl(decoder);
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
final ByteBuffer buffer = ByteBuffer.allocate(64);
decoder.setByteBuffer(buffer);
encoder.setByteBuffer(buffer);
encoder.writeString("skipped");
encoder.writeString("read");
buffer.clear();
TypeConstructor<?> stringType = decoder.readConstructor();
assertEquals(String.class, stringType.getTypeClass());
stringType.skipValue();
String result = decoder.readString();
assertEquals("read", result);
}
@Test
public void testEncodeAndDecodeEmptyString() {
final DecoderImpl decoder = new DecoderImpl();
final EncoderImpl encoder = new EncoderImpl(decoder);
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
final ByteBuffer buffer = ByteBuffer.allocate(64);
encoder.setByteBuffer(buffer);
decoder.setByteBuffer(buffer);
encoder.writeString("a");
encoder.writeString("");
encoder.writeString("b");
buffer.clear();
TypeConstructor<?> stringType = decoder.readConstructor();
assertEquals(String.class, stringType.getTypeClass());
stringType.skipValue();
String result = decoder.readString();
assertEquals("", result);
result = decoder.readString();
assertEquals("b", result);
}
@Test
public void testEmptyShortStringEncode() {
doTestEmptyStringEncodeAsGivenType(EncodingCodes.STR8);
}
@Test
public void testEmptyLargeStringEncode() {
doTestEmptyStringEncodeAsGivenType(EncodingCodes.STR32);
}
public void doTestEmptyStringEncodeAsGivenType(byte encodingCode) {
final DecoderImpl decoder = new DecoderImpl();
final EncoderImpl encoder = new EncoderImpl(decoder);
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
final ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.put(encodingCode);
buffer.putInt(0);
buffer.clear();
byte[] copy = new byte[buffer.remaining()];
buffer.get(copy);
CompositeReadableBuffer composite = new CompositeReadableBuffer();
composite.append(copy);
decoder.setBuffer(composite);
TypeConstructor<?> stringType = decoder.readConstructor();
assertEquals(String.class, stringType.getTypeClass());
String result = (String) stringType.readValue();
assertEquals("", result);
}
// build up some test data with a set of suitable Unicode characters
private static List<String> generateTestData()
{
return new LinkedList<String>()
{
private static final long serialVersionUID = 7331717267070233454L;
{
// non-surrogate pair blocks
addAll(getAllStringsFromUnicodeBlocks(UnicodeBlock.BASIC_LATIN,
UnicodeBlock.LATIN_1_SUPPLEMENT,
UnicodeBlock.GREEK,
UnicodeBlock.LETTERLIKE_SYMBOLS));
// blocks with surrogate pairs
// TODO: restore others when Java 7 is baseline
addAll(getAllStringsFromUnicodeBlocks(UnicodeBlock.LINEAR_B_SYLLABARY,
/*UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS,*/
UnicodeBlock.MUSICAL_SYMBOLS,
/*UnicodeBlock.EMOTICONS,*/
/*UnicodeBlock.PLAYING_CARDS,*/
UnicodeBlock.BOX_DRAWING,
UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS,
UnicodeBlock.PRIVATE_USE_AREA,
UnicodeBlock.SUPPLEMENTARY_PRIVATE_USE_AREA_A,
UnicodeBlock.SUPPLEMENTARY_PRIVATE_USE_AREA_B));
// some additional combinations of characters that could cause problems to the encoder
String[] boxDrawing = getAllStringsFromUnicodeBlocks(UnicodeBlock.BOX_DRAWING).toArray(new String[0]);
String[] halfFullWidthForms = getAllStringsFromUnicodeBlocks(UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS).toArray(new String[0]);
for (int i = 0; i < halfFullWidthForms.length; i++)
{
add(halfFullWidthForms[i] + boxDrawing[i % boxDrawing.length]);
}
}
};
}
@Test
public void testEncodeAndDecodeUsingWritableBufferDefaultPutString() {
final DecoderImpl decoder = new DecoderImpl();
final EncoderImpl encoder = new EncoderImpl(decoder);
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
for (final String input : TEST_DATA) {
final WritableBufferWithoutPutStringOverride sink = new WritableBufferWithoutPutStringOverride(16);
final AmqpValue inputValue = new AmqpValue(input);
encoder.setByteBuffer(sink);
encoder.writeObject(inputValue);
ReadableBuffer source = new ReadableBuffer.ByteBufferReader(ByteBuffer.wrap(sink.getArray(), 0, sink.getArrayLength()));
decoder.setBuffer(source);
final AmqpValue outputValue = (AmqpValue) decoder.readObject();
assertEquals("Failed to round trip String correctly: ", input, outputValue.getValue());
}
}
/**
* Test class which implements WritableBuffer but does not override the default put(String)
* method, used to verify the default method is in place and works.
*/
private static final class WritableBufferWithoutPutStringOverride implements WritableBuffer {
private final ByteBufferWrapper delegate;
public WritableBufferWithoutPutStringOverride(int capacity) {
delegate = WritableBuffer.ByteBufferWrapper.allocate(capacity);
}
public byte[] getArray() {
return delegate.byteBuffer().array();
}
public int getArrayLength() {
return delegate.byteBuffer().position();
}
@Override
public void put(byte b) {
delegate.put(b);
}
@Override
public void putShort(short value) {
delegate.putShort(value);
}
@Override
public void putInt(int value) {
delegate.putInt(value);
}
@Override
public void putLong(long value) {
delegate.putLong(value);
}
@Override
public void putFloat(float value) {
delegate.putFloat(value);
}
@Override
public void putDouble(double value) {
delegate.putDouble(value);
}
@Override
public void put(byte[] src, int offset, int length) {
delegate.put(src, offset, length);
}
@Override
public boolean hasRemaining() {
return delegate.hasRemaining();
}
@Override
public int remaining() {
return delegate.remaining();
}
@Override
public int position() {
return delegate.position();
}
@Override
public void position(int position) {
delegate.position(position);
}
@Override
public void put(ByteBuffer payload) {
delegate.put(payload);
}
@Override
public int limit() {
return delegate.limit();
}
@Override
public void put(ReadableBuffer src) {
delegate.put(src);
}
}
}