/*
* Copyright (c) 2014 Oculus Info Inc.
* http://www.oculusinfo.com/
*
* Released under the MIT License.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oculusinfo.binning.io.serialization.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.oculusinfo.binning.impl.DenseTileMultiSliceView;
import com.oculusinfo.binning.impl.SparseTileData;
import org.apache.avro.file.CodecFactory;
import org.junit.Assert;
import org.junit.Test;
import com.oculusinfo.binning.TileData;
import com.oculusinfo.binning.TileIndex;
import com.oculusinfo.binning.impl.DenseTileData;
import com.oculusinfo.binning.io.serialization.TileSerializer;
public class PrimitiveArrayAvroSerializerTest {
@SafeVarargs
final <T> void testRoundTrip(Class<? extends T> type, int arraySize, T... data) throws Exception {
TileSerializer<List<T>> serializer = new PrimitiveArrayAvroSerializer<T>(type, CodecFactory.nullCodec());
// Create our tile
int n = (int) Math.ceil(data.length/(double)arraySize);
int size = (int) Math.ceil(Math.sqrt(n));
TileData<List<T>> input = new DenseTileData<>(new TileIndex(0, 0, 0, size, size));
for (int y=0; y<size; ++y) {
for (int x=0; x<size; ++x) {
int i = ((x+size*y) % data.length)*arraySize;
List<T> list = new ArrayList<>(arraySize);
for (int j=0; j<arraySize; ++j)
if ((i+j) < data.length)
list.add(data[i+j]);
input.setBin(x, y, list);
}
}
// Send it round-trip through serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(input, baos);
baos.flush();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
TileData<List<T>> output = serializer.deserialize(new TileIndex(1, 1, 1, size, size), bais);
// Test to make sure output matches input.
Assert.assertEquals(input.getDefinition(), output.getDefinition());
for (int y=0; y<size; ++y) {
for (int x=0; x<size; ++x) {
assertListsEqual(input.getBin(x, y), output.getBin(x, y));
}
}
}
private <T> void assertListsEqual (List<T> a, List<T> b) {
Assert.assertEquals(a.size(), b.size());
for (int i=0; i<a.size(); ++i) {
Assert.assertEquals(a.get(i), b.get(i));
}
}
@Test
public void testBoolean () throws Exception {
testRoundTrip(Boolean.class, 3,
true, false, true,
true, false, true,
false, false, true,
false, false, false);
}
@Test
public void testInteger () throws Exception {
testRoundTrip(Integer.class, 2,
0, 0,
1, 1,
2, 4,
3, 9);
}
@Test
public void testLong () throws Exception {
testRoundTrip(Long.class, 3,
0L, 1L, 8L,
27L, 64L, 125L,
216L, 343L, 512L,
729L, 1000L, 1331L);
}
@Test
public void testFloat () throws Exception {
testRoundTrip(Float.class, 1,
0.0f, 0.5f, 0.333f, 0.25f, 0.2f, 0.166f, 0.142857f, 0.125f);
}
@Test
public void testDouble () throws Exception {
testRoundTrip(Double.class, 1,
0.0, 1.1, 2.4, 3.9, 4.16, 5.25, 6.36, 7.49, 8.64);
}
@Test
public void testBytes () throws Exception {
testRoundTrip(ByteBuffer.class, 1,
ByteBuffer.wrap(new byte[] {}),
ByteBuffer.wrap(new byte[] {(byte) 1}),
ByteBuffer.wrap(new byte[] {(byte) 2, (byte) 4}),
ByteBuffer.wrap(new byte[] {(byte) 3, (byte) 9, (byte) 27}));
}
@Test
public void testString () throws Exception {
testRoundTrip(String.class, 4,
"a", "aa", "aaa", "aaaa",
"b", "bb", "bbb", "bbbb",
"c", "cc", "ccc", "cccc",
"d", "dd", "ddd", "dddd");
}
@Test(expected=IllegalArgumentException.class)
public void testShort () throws Exception {
testRoundTrip(Short.class, 1, (short)0);
}
@Test(expected=IllegalArgumentException.class)
public void testByte () throws Exception {
testRoundTrip(Byte.class, 1, (byte)0);
}
@Test(expected=IllegalArgumentException.class)
public void testReferenceType () throws Exception {
List<Integer> sample = new ArrayList<>();
testRoundTrip(sample.getClass(), 1, sample);
}
// Test reading and writing of dense tiles
@Test
public void testNullBinsInDenseTile () throws Exception {
TileData<List<Integer>> inputTile = new DenseTileData<>(new TileIndex(0, 0, 0, 4, 4));
inputTile.setBin(0, 0, Arrays.asList( 1, 2, 3, 4));
inputTile.setBin(1, 1, Arrays.asList( 2, 4, 6, 8));
inputTile.setBin(2, 2, Arrays.asList( 3, 6, 9, 12));
inputTile.setBin(3, 3, Arrays.asList( 4, 8, 12, 16));
TileSerializer<List<Integer>> serializer = new PrimitiveArrayAvroSerializer<>(Integer.class, CodecFactory.nullCodec());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(inputTile, baos);
baos.flush();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
TileData<List<Integer>> outputTile = serializer.deserialize(new TileIndex(0, 0, 0, 4, 4), bais);
for (int x=0; x<4; ++x) {
for (int y=0; y<4; ++y) {
String binName = "Bin ["+x+", "+y+"]";
List<Integer> input = inputTile.getBin(x, y);
List<Integer> output = outputTile.getBin(x, y);
if (null == input) {
Assert.assertTrue(binName, null == output || output.size() == 0);
} else {
Assert.assertEquals(binName, input.size(), output.size());
for (int z=0; z<input.size(); ++z) {
Assert.assertEquals(input.get(z), output.get(z));
}
}
}
}
}
// Test reading and writing of slices of dense tiles
@Test
public void testNullBinsInDenseTileSlice () throws Exception {
TileData<List<Integer>> inputTile = new DenseTileData<>(new TileIndex(0, 0, 0, 4, 4));
inputTile.setBin(0, 0, Arrays.asList(1));
inputTile.setBin(1, 1, Arrays.asList( 2, 4));
inputTile.setBin(2, 2, Arrays.asList( 3, 6, 9));
inputTile.setBin(3, 3, Arrays.asList( 4, 8, 12, 16));
TileSerializer<List<Integer>> serializer = new PrimitiveArrayAvroSerializer<>(Integer.class, CodecFactory.nullCodec());
// Test a fifth slice too, even though it should be totally empty
for (int i=0; i<5; ++i) {
TileData<List<Integer>> inputSlice = new DenseTileMultiSliceView<Integer>(inputTile, i, i);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(inputSlice, baos);
baos.flush();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
TileData<List<Integer>> outputTile = serializer.deserialize(new TileIndex(0, 0, 0, 4, 4), bais);
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 4; ++y) {
String binName = "Bin [" + x + ", " + y + "]";
List<Integer> input = inputSlice.getBin(x, y);
List<Integer> output = outputTile.getBin(x, y);
if (null == input) {
Assert.assertTrue(binName, null == output || output.size() == 0);
} else {
Assert.assertEquals(binName, input.size(), output.size());
for (int z = 0; z < input.size(); ++z) {
Assert.assertEquals(input.get(z), output.get(z));
}
}
}
}
}
}
@Test
public void testDenseDefaults () throws Exception {
TileSerializer<List<Integer>> serializer = new PrimitiveArrayAvroSerializer<>(Integer.class, CodecFactory.nullCodec());
DenseTileData<List<Integer>> denseBase = new DenseTileData<List<Integer>>(new TileIndex(0, 0, 0, 2, 2), Arrays.asList(1, 2, 3));
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(denseBase, baos);
baos.flush();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
TileData<List<Integer>> out = serializer.deserialize(denseBase.getDefinition(), bais);
Assert.assertTrue(out instanceof DenseTileData<?>);
List<Integer> baseDefault = denseBase.getDefaultValue();
List<Integer> readDefault = ((DenseTileData<List<Integer>>) out).getDefaultValue();
Assert.assertEquals(baseDefault.size(), readDefault.size());
for (int i=0; i<baseDefault.size(); ++i) {
Assert.assertEquals(baseDefault.get(i), readDefault.get(i));
}
}
}
@Test
public void testSparseDefaults () throws Exception {
TileSerializer<List<Integer>> serializer = new PrimitiveArrayAvroSerializer<>(Integer.class, CodecFactory.nullCodec());
SparseTileData<List<Integer>> sparseBase = new SparseTileData<List<Integer>>(new TileIndex(0, 0, 0, 2, 2), Arrays.asList(4, 5, 6));
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(sparseBase, baos);
baos.flush();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
TileData<List<Integer>> out = serializer.deserialize(sparseBase.getDefinition(), bais);
Assert.assertTrue(out instanceof SparseTileData<?>);
List<Integer> baseDefault = sparseBase.getDefaultValue();
List<Integer> readDefault = ((SparseTileData<List<Integer>>) out).getDefaultValue();
Assert.assertEquals(baseDefault.size(), readDefault.size());
for (int i=0; i<baseDefault.size(); ++i) {
Assert.assertEquals(baseDefault.get(i), readDefault.get(i));
}
}
}
}