/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* contributors. All rights reserved.
*
* Licensed 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 com.noctarius.tengi.spi.serialization.codec.impl.utf8;
import com.noctarius.tengi.spi.buffer.MemoryBuffer;
import com.noctarius.tengi.spi.serialization.Serializer;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableDecoder;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableEncoder;
import com.noctarius.tengi.testing.AbstractTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.UTFDataFormatException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class UTF8CodecTestCase
extends AbstractTestCase {
private static final Random RANDOM = new Random();
@Parameterized.Parameters(name = "fastString={0}, utfWriterType={1}")
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[][]{ //
{false, UtfWriterType.DEFAULT}, {false, UtfWriterType.UNSAFE}, //
{false, UtfWriterType.REFLECTION}, {true, UtfWriterType.DEFAULT}, //
{true, UtfWriterType.UNSAFE}, {true, UtfWriterType.REFLECTION}});
}
private final boolean fastStringEnabled;
private final UtfWriterType utfWriterType;
public UTF8CodecTestCase(boolean fastStringEnabled, UtfWriterType utfWriterType) {
this.fastStringEnabled = fastStringEnabled;
this.utfWriterType = utfWriterType;
}
@Test
public void test_empty_text()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
MemoryBuffer memoryBuffer = createMemoryBuffer();
byte[] buffer = new byte[1024];
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
utf8Codec.writeUTF0(encoder, "", buffer);
utf8Codec.writeUTF0(encoder, "some other value", buffer);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
String result1 = utf8Codec.readUTF0(decoder, buffer);
String result2 = utf8Codec.readUTF0(decoder, buffer);
assertEquals("", result1);
assertEquals("some other value", result2);
}
}
@Test
public void test_multiple_texts_in_a_row()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
for (int i = 0; i < 100; i++) {
MemoryBuffer memoryBuffer = createMemoryBuffer();
String[] values = new String[10];
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
for (int o = 0; o < values.length; o++) {
values[o] = randomString(i);
utf8Codec.writeUTF0(encoder, values[o], buffer);
}
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
for (int o = 0; o < values.length; o++) {
String result = utf8Codec.readUTF0(decoder, buffer);
assertEquals(values[o], result);
}
}
}
}
@Test
public void test_short_sized_text_1_chunk()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
for (int i = 2; i < 100; i += 2) {
MemoryBuffer memoryBuffer = createMemoryBuffer();
String randomString = randomString(i * 100);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
utf8Codec.writeUTF0(encoder, randomString, buffer);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
String result = utf8Codec.readUTF0(decoder, buffer);
assertEquals(randomString, result);
}
}
}
@Test
public void test_mid_sized_text_2_chunks()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
for (int i = 170; i < 300; i += 2) {
MemoryBuffer memoryBuffer = createMemoryBuffer();
String randomString = randomString(i * 100);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
utf8Codec.writeUTF0(encoder, randomString, buffer);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
String result = utf8Codec.readUTF0(decoder, buffer);
assertEquals(randomString, result);
}
}
}
@Test
public void test_long_sized_text_3_chunks()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
for (int i = 330; i < 900; i += 5) {
MemoryBuffer memoryBuffer = createMemoryBuffer();
String randomString = randomString(i * 100);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
utf8Codec.writeUTF0(encoder, randomString, buffer);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
String result = utf8Codec.readUTF0(decoder, buffer);
assertEquals(randomString, result);
}
}
}
@Test
public void test_multibyte_char_at_position_that_even_multiple_of_buffer_size()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
for (int i : new int[]{50240, 100240, 80240}) {
MemoryBuffer memoryBuffer = createMemoryBuffer();
String originalString = createString(i);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
utf8Codec.writeUTF0(encoder, originalString, buffer);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
String result = utf8Codec.readUTF0(decoder, buffer);
assertEquals(originalString, result);
}
}
}
@Test(expected = NegativeArraySizeException.class)
public void test_integer_overflow_on_broken_packet()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
MemoryBuffer memoryBuffer = createMemoryBuffer();
// Create broken packet (byte stream)!
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
encoder.writeInt32("length1", Integer.MAX_VALUE + 1);
encoder.writeInt32("length2", Integer.MAX_VALUE + 1);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
utf8Codec.readUTF0(decoder, buffer);
}
}
@Test(expected = UTFDataFormatException.class)
public void test_length_check_on_broken_packet()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
MemoryBuffer memoryBuffer = createMemoryBuffer();
// Create broken packet (byte stream)!
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
encoder.writeBoolean("null", false);
encoder.writeInt32("length1", Integer.MAX_VALUE + 1);
encoder.writeInt32("length2", Integer.MAX_VALUE);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
utf8Codec.readUTF0(decoder, buffer);
}
}
@Test
public void test_substring()
throws Exception {
UTF8Codec utf8Codec = newUTF8Codec(fastStringEnabled, utfWriterType);
if (utf8Codec == null) {
System.err.println("Ignoring test... " + utfWriterType + " is not available!");
return;
}
Serializer serializer = createSerializer();
byte[] buffer = new byte[1024];
String original = "1234abcd";
String substring = original.substring(4);
MemoryBuffer memoryBuffer = createMemoryBuffer();
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
utf8Codec.writeUTF0(encoder, substring, buffer);
}
try (AutoClosableDecoder decoder = serializer.retrieveDecoder(memoryBuffer)) {
String result = decoder.readString();
assertEquals(substring, result);
}
}
private static UTF8Codec newUTF8Codec(boolean fastStringEnabled, UtfWriterType utfWriterType) {
UtfWriter utfWriter;
switch (utfWriterType) {
case UNSAFE:
UnsafeBasedCharArrayUtfWriter unsafeBasedWriter = new UnsafeBasedCharArrayUtfWriter();
if (!unsafeBasedWriter.isAvailable()) {
return null;
}
utfWriter = unsafeBasedWriter;
break;
case REFLECTION:
ReflectionBasedCharArrayUtfWriter reflectionBasedWriter = new ReflectionBasedCharArrayUtfWriter();
if (!reflectionBasedWriter.isAvailable()) {
return null;
}
utfWriter = reflectionBasedWriter;
break;
default:
utfWriter = new StringBasedUtfWriter();
}
StringCreator stringCreator = UTF8Codec.createStringCreator(fastStringEnabled);
if (fastStringEnabled) {
assertTrue(stringCreator.getClass().toString().contains("FastStringCreator"));
}
return new UTF8Codec(stringCreator, utfWriter);
}
private static String createString(int length) {
char[] c = new char[length];
for (int i = 0; i < c.length; i++) {
c[i] = 'a';
}
c[10240] = 'å';
return new String(c);
}
private static String randomString(int count) {
return randomString(count, 0, 0, false, false, null, RANDOM);
}
private static String randomAlphaNumeric(int count) {
return randomString(count, 0, 0, true, true, null, RANDOM);
}
/*
* Thanks to Apache Commons:
* org.apache.commons.lang.RandomStringUtils
*/
private static String randomString(int count, int start, int end, boolean letters, //
boolean numbers, char[] chars, Random random) {
if (count == 0) {
return "";
} else if (count < 0) {
throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
}
if (start == 0 && end == 0) {
end = 'z' + 1;
start = ' ';
if (!letters && !numbers) {
start = 0;
end = Integer.MAX_VALUE;
}
}
char[] buffer = new char[count];
int gap = end - start;
while (count-- != 0) {
char ch;
if (chars == null) {
ch = (char) (random.nextInt(gap) + start);
} else {
ch = chars[random.nextInt(gap) + start];
}
if (letters && Character.isLetter(ch) || numbers && Character.isDigit(ch) || !letters && !numbers) {
if (ch >= 56320 && ch <= 57343) {
if (count == 0) {
count++;
} else {
// low surrogate, insert high surrogate after putting it in
buffer[count] = ch;
count--;
buffer[count] = (char) (55296 + random.nextInt(128));
}
} else if (ch >= 55296 && ch <= 56191) {
if (count == 0) {
count++;
} else {
// high surrogate, insert low surrogate before putting it in
buffer[count] = (char) (56320 + random.nextInt(128));
count--;
buffer[count] = ch;
}
} else if (ch >= 56192 && ch <= 56319) {
// private high surrogate, no effing clue, so skip it
count++;
} else {
buffer[count] = ch;
}
} else {
count++;
}
}
return new String(buffer);
}
private static enum UtfWriterType {
UNSAFE,
REFLECTION,
DEFAULT
}
}