diff --git a/README.md b/README.md
index 3437b8a..895dea1 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ When using Maven: The latest version, 1.0.4, is not yet available on Maven centr
io.github.fastfilter
fastfilter
- 1.0.4
+ 1.0.5
### Gradle
@@ -110,7 +110,7 @@ When using Maven: The latest version, 1.0.4, is not yet available on Maven centr
}
dependencies {
- implementation 'io.github.fastfilter.fastfilter_java:fastfilter:1.0.4'
+ implementation 'io.github.fastfilter.fastfilter_java:fastfilter:1.0.5'
}
### Maven Central (version 1.0.2)
diff --git a/fastfilter/pom.xml b/fastfilter/pom.xml
index 2e84df2..5178f9f 100644
--- a/fastfilter/pom.xml
+++ b/fastfilter/pom.xml
@@ -3,7 +3,7 @@
io.github.fastfilter
fastfilter_java
- 1.0.5-SNAPSHOT
+ 1.0.6-SNAPSHOT
4.0.0
diff --git a/fastfilter/src/main/java/org/fastfilter/Filter.java b/fastfilter/src/main/java/org/fastfilter/Filter.java
index 05bfe43..83bd4f6 100644
--- a/fastfilter/src/main/java/org/fastfilter/Filter.java
+++ b/fastfilter/src/main/java/org/fastfilter/Filter.java
@@ -1,5 +1,7 @@
package org.fastfilter;
+import java.io.IOException;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
@@ -85,4 +87,15 @@ default int getSerializedSize() {
default void serialize(ByteBuffer buffer) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Serializes the filter state into the provided {@code OutputStream}.
+ *
+ * @param out the output stream where the serialized state of the filter will be written
+ * @throws IOException if writing to the stream fails
+ * @throws UnsupportedOperationException if the operation is not supported by the filter implementation
+ */
+ default void serialize(OutputStream out) throws IOException {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/fastfilter/src/main/java/org/fastfilter/FilterType.java b/fastfilter/src/main/java/org/fastfilter/FilterType.java
index ac69e1a..22ac83b 100644
--- a/fastfilter/src/main/java/org/fastfilter/FilterType.java
+++ b/fastfilter/src/main/java/org/fastfilter/FilterType.java
@@ -8,6 +8,7 @@
import org.fastfilter.cuckoo.CuckooPlus16;
import org.fastfilter.cuckoo.CuckooPlus8;
import org.fastfilter.gcs.GolombCompressedSet;
+import org.fastfilter.ribbon.RibbonNaiveFilter;
import org.fastfilter.xor.*;
import org.fastfilter.xorplus.XorPlus8;
@@ -87,6 +88,12 @@ public Filter construct(long[] keys, int setting) {
return XorPlus8.construct(keys);
}
},
+ RIBBON_NAIVE {
+ @Override
+ public Filter construct(long[] keys, int setting) {
+ return RibbonNaiveFilter.construct(keys);
+ }
+ },
CUCKOO_8 {
@Override
public Filter construct(long[] keys, int setting) {
diff --git a/fastfilter/src/main/java/org/fastfilter/ribbon/RibbonNaiveFilter.java b/fastfilter/src/main/java/org/fastfilter/ribbon/RibbonNaiveFilter.java
new file mode 100644
index 0000000..c290a81
--- /dev/null
+++ b/fastfilter/src/main/java/org/fastfilter/ribbon/RibbonNaiveFilter.java
@@ -0,0 +1,227 @@
+package org.fastfilter.ribbon;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import org.fastfilter.Filter;
+import org.fastfilter.utils.Hash;
+
+/**
+ * A (non-homogeneous) Ribbon filter with a fixed ribbon width w = 64.
+ *
+ * A Ribbon filter is a retrieval data structure: for every key x in the
+ * set, solving a banded linear system over GF(2) yields a small function
+ * f(x) that returns an r-bit fingerprint. Construction can fail (linear
+ * dependence among the equations) and is retried with a new seed, and -
+ * if all seed attempts at a given slot count fail - with more slots.
+ *
+ * Martin Dietzfelbinger, Stefan Walzer, [Efficient Gauss Elimination for
+ * Near-Quadratic Matrices with One Short Random Block per
+ * Row](https://arxiv.org/abs/1907.04750), ESA 2019.
+ *
+ * Peter C. Dillinger, Stefan Walzer, [Ribbon filter: practically smaller
+ * than Bloom and Xor](https://arxiv.org/abs/2103.02515), arXiv:2103.02515,
+ * 2021.
+ *
+ * This is the "naive" query implementation: each lookup does an O(w * r)
+ * per-column dot product against the solved band, rather than the
+ * Interleaved Column-Major Layout (ICML) the papers above use for fast
+ * queries. It is kept under this name so a future ICML-backed
+ * implementation can use the plain RibbonFilter / RIBBON name once it
+ * exists.
+ */
+public class RibbonNaiveFilter implements Filter {
+
+ private static final int DEFAULT_RESULT_BITS = 8;
+ private static final int COEFF_BITS = 64;
+ private static final int MAX_ATTEMPTS_PER_SIZE = 256;
+ private static final double INITIAL_OVERHEAD_FACTOR = 0.10;
+ private static final double OVERHEAD_GROWTH_FACTOR = 1.5;
+
+ private final int size;
+ private final int resultBits;
+ private final int numSlots;
+ private final int numStarts;
+ private final long seed;
+ private final byte[] solution;
+ private final int bitCount;
+
+ private RibbonNaiveFilter(int size, int resultBits, int numSlots, int numStarts, long seed, byte[] solution) {
+ this.size = size;
+ this.resultBits = resultBits;
+ this.numSlots = numSlots;
+ this.numStarts = numStarts;
+ this.seed = seed;
+ this.solution = solution;
+ this.bitCount = numSlots * resultBits;
+ }
+
+ public static RibbonNaiveFilter construct(long[] keys) {
+ return construct(keys, DEFAULT_RESULT_BITS);
+ }
+
+ public static RibbonNaiveFilter construct(long[] keys, int resultBits) {
+ if (resultBits < 1 || resultBits > 8) {
+ throw new IllegalArgumentException("resultBits must be between 1 and 8");
+ }
+ int size = keys.length;
+ double overheadFactor = INITIAL_OVERHEAD_FACTOR;
+ while (true) {
+ int numStarts = (int) Math.ceil(size * (1 + overheadFactor));
+ if (numStarts < 1) {
+ numStarts = 1;
+ }
+ int numSlots = numStarts + COEFF_BITS - 1;
+ for (int attempt = 0; attempt < MAX_ATTEMPTS_PER_SIZE; attempt++) {
+ long seed = Hash.randomSeed();
+ long[] coeff = new long[numSlots];
+ byte[] result = new byte[numSlots];
+ if (band(keys, seed, numStarts, resultBits, coeff, result)) {
+ byte[] solution = backSubstitute(coeff, result, numSlots, resultBits);
+ return new RibbonNaiveFilter(size, resultBits, numSlots, numStarts, seed, solution);
+ }
+ }
+ overheadFactor *= OVERHEAD_GROWTH_FACTOR;
+ }
+ }
+
+ private static boolean band(long[] keys, long seed, int numStarts, int resultBits, long[] coeff, byte[] result) {
+ int mask = (1 << resultBits) - 1;
+ for (long key : keys) {
+ long hash = Hash.hash64(key, seed);
+ int s = Hash.reduce((int) hash, numStarts);
+ long c = Long.rotateLeft(hash, 21) | 1L;
+ int r = (int) (Long.rotateLeft(hash, 42) & mask);
+ while (true) {
+ int i = Long.numberOfTrailingZeros(c);
+ int p = s + i;
+ c >>>= i;
+ if (coeff[p] == 0) {
+ coeff[p] = c;
+ result[p] = (byte) r;
+ break;
+ }
+ c ^= coeff[p];
+ r ^= result[p];
+ s = p;
+ if (c == 0) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static byte[] backSubstitute(long[] coeff, byte[] result, int numSlots, int resultBits) {
+ byte[] solution = new byte[numSlots];
+ int mask = (1 << resultBits) - 1;
+ for (int i = numSlots - 1; i >= 0; i--) {
+ long c = coeff[i];
+ if (c == 0) {
+ continue;
+ }
+ int contribution = 0;
+ long rest = c & ~1L;
+ while (rest != 0) {
+ int k = Long.numberOfTrailingZeros(rest);
+ rest &= rest - 1;
+ contribution ^= solution[i + k];
+ }
+ solution[i] = (byte) ((result[i] ^ contribution) & mask);
+ }
+ return solution;
+ }
+
+ @Override
+ public boolean mayContain(long key) {
+ long hash = Hash.hash64(key, seed);
+ int s = Hash.reduce((int) hash, numStarts);
+ long c = Long.rotateLeft(hash, 21) | 1L;
+ int mask = (1 << resultBits) - 1;
+ int expected = (int) (Long.rotateLeft(hash, 42) & mask);
+
+ int actual = 0;
+ for (int j = 0; j < resultBits; j++) {
+ long columnBits = 0;
+ for (int k = 0; k < COEFF_BITS; k++) {
+ if (((solution[s + k] >>> j) & 1) != 0) {
+ columnBits |= (1L << k);
+ }
+ }
+ int bit = Long.bitCount(columnBits & c) & 1;
+ actual |= bit << j;
+ }
+ return actual == expected;
+ }
+
+ @Override
+ public long getBitCount() {
+ return bitCount;
+ }
+
+ @Override
+ public int getSerializedSize() {
+ return Integer.BYTES * 5 + Long.BYTES + solution.length * Byte.BYTES;
+ }
+
+ @Override
+ public void serialize(ByteBuffer buffer) {
+ if (buffer.remaining() < getSerializedSize()) {
+ throw new IllegalArgumentException("Buffer too small");
+ }
+ buffer.putInt(size);
+ buffer.putInt(resultBits);
+ buffer.putInt(numSlots);
+ buffer.putInt(numStarts);
+ buffer.putLong(seed);
+ buffer.putInt(solution.length);
+ buffer.put(solution);
+ }
+
+ public static RibbonNaiveFilter deserialize(ByteBuffer buffer) {
+ if (buffer.remaining() < Integer.BYTES * 5 + Long.BYTES) {
+ throw new IllegalArgumentException("Buffer too small");
+ }
+ final int size = buffer.getInt();
+ final int resultBits = buffer.getInt();
+ final int numSlots = buffer.getInt();
+ final int numStarts = buffer.getInt();
+ final long seed = buffer.getLong();
+ final int len = buffer.getInt();
+ if (buffer.remaining() < len * Byte.BYTES) {
+ throw new IllegalArgumentException("Buffer too small");
+ }
+ final byte[] solution = new byte[len];
+ buffer.get(solution);
+ return new RibbonNaiveFilter(size, resultBits, numSlots, numStarts, seed, solution);
+ }
+
+ @Override
+ public void serialize(OutputStream out) throws IOException {
+ DataOutputStream dout = new DataOutputStream(out);
+ dout.writeInt(size);
+ dout.writeInt(resultBits);
+ dout.writeInt(numSlots);
+ dout.writeInt(numStarts);
+ dout.writeLong(seed);
+ dout.writeInt(solution.length);
+ dout.write(solution);
+ }
+
+ public static RibbonNaiveFilter deserialize(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ final int size = din.readInt();
+ final int resultBits = din.readInt();
+ final int numSlots = din.readInt();
+ final int numStarts = din.readInt();
+ final long seed = din.readLong();
+ final int len = din.readInt();
+ final byte[] solution = new byte[len];
+ din.readFully(solution);
+ return new RibbonNaiveFilter(size, resultBits, numSlots, numStarts, seed, solution);
+ }
+}
diff --git a/fastfilter/src/main/java/org/fastfilter/xor/Xor16.java b/fastfilter/src/main/java/org/fastfilter/xor/Xor16.java
index 603a3f1..265ec12 100644
--- a/fastfilter/src/main/java/org/fastfilter/xor/Xor16.java
+++ b/fastfilter/src/main/java/org/fastfilter/xor/Xor16.java
@@ -1,5 +1,10 @@
package org.fastfilter.xor;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.fastfilter.Filter;
@@ -199,4 +204,27 @@ public static Xor16 deserialize(ByteBuffer buffer) {
return new Xor16(blockLength, bitCount, seed, fingerprints);
}
+
+ public void serialize(OutputStream out) throws IOException {
+ DataOutputStream dout = new DataOutputStream(out);
+ dout.writeInt(blockLength);
+ dout.writeLong(seed);
+ dout.writeInt(fingerprints.length);
+ for (final short fp : fingerprints) {
+ dout.writeShort(fp);
+ }
+ }
+
+ public static Xor16 deserialize(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ final int blockLength = din.readInt();
+ final long seed = din.readLong();
+ final int len = din.readInt();
+ final short[] fingerprints = new short[len];
+ for (int i = 0; i < len; i++) {
+ fingerprints[i] = din.readShort();
+ }
+ final int bitCount = len * BITS_PER_FINGERPRINT;
+ return new Xor16(blockLength, bitCount, seed, fingerprints);
+ }
}
diff --git a/fastfilter/src/main/java/org/fastfilter/xor/Xor8.java b/fastfilter/src/main/java/org/fastfilter/xor/Xor8.java
index 142fb87..4b7c5b6 100644
--- a/fastfilter/src/main/java/org/fastfilter/xor/Xor8.java
+++ b/fastfilter/src/main/java/org/fastfilter/xor/Xor8.java
@@ -241,4 +241,22 @@ public static Xor8 deserialize(ByteBuffer buffer) {
return new Xor8(size, seed, fingerprints);
}
+
+ public void serialize(OutputStream out) throws IOException {
+ DataOutputStream dout = new DataOutputStream(out);
+ dout.writeInt(size);
+ dout.writeLong(seed);
+ dout.writeInt(fingerprints.length);
+ dout.write(fingerprints);
+ }
+
+ public static Xor8 deserialize(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ final int size = din.readInt();
+ final long seed = din.readLong();
+ final int len = din.readInt();
+ final byte[] fingerprints = new byte[len];
+ din.readFully(fingerprints);
+ return new Xor8(size, seed, fingerprints);
+ }
}
diff --git a/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse16.java b/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse16.java
index b0ef76a..588e70c 100644
--- a/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse16.java
+++ b/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse16.java
@@ -1,5 +1,10 @@
package org.fastfilter.xor;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.fastfilter.Filter;
@@ -325,4 +330,29 @@ public static XorBinaryFuse16 deserialize(ByteBuffer buffer) {
return new XorBinaryFuse16(segmentCount, segmentLength, seed, fingerprints);
}
+
+ public void serialize(OutputStream out) throws IOException {
+ DataOutputStream dout = new DataOutputStream(out);
+ dout.writeInt(segmentLength);
+ dout.writeInt(segmentCountLength);
+ dout.writeLong(seed);
+ dout.writeInt(fingerprints.length);
+ for (final short fp : fingerprints) {
+ dout.writeShort(fp);
+ }
+ }
+
+ public static XorBinaryFuse16 deserialize(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ final int segmentLength = din.readInt();
+ final int segmentCountLength = din.readInt();
+ final long seed = din.readLong();
+ final int len = din.readInt();
+ final short[] fingerprints = new short[len];
+ for (int i = 0; i < len; i++) {
+ fingerprints[i] = din.readShort();
+ }
+ final int segmentCount = segmentCountLength / segmentLength;
+ return new XorBinaryFuse16(segmentCount, segmentLength, seed, fingerprints);
+ }
}
diff --git a/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse32.java b/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse32.java
index fc1a01b..d226081 100644
--- a/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse32.java
+++ b/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse32.java
@@ -1,5 +1,10 @@
package org.fastfilter.xor;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -313,4 +318,29 @@ public static XorBinaryFuse32 deserialize(ByteBuffer buffer) {
return new XorBinaryFuse32(segmentCount, segmentLength, seed, fingerprints);
}
+
+ public void serialize(OutputStream out) throws IOException {
+ DataOutputStream dout = new DataOutputStream(out);
+ dout.writeInt(segmentLength);
+ dout.writeInt(segmentCountLength);
+ dout.writeLong(seed);
+ dout.writeInt(fingerprints.length);
+ for (final int fp : fingerprints) {
+ dout.writeInt(fp);
+ }
+ }
+
+ public static XorBinaryFuse32 deserialize(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ final int segmentLength = din.readInt();
+ final int segmentCountLength = din.readInt();
+ final long seed = din.readLong();
+ final int len = din.readInt();
+ final int[] fingerprints = new int[len];
+ for (int i = 0; i < len; i++) {
+ fingerprints[i] = din.readInt();
+ }
+ final int segmentCount = segmentCountLength / segmentLength;
+ return new XorBinaryFuse32(segmentCount, segmentLength, seed, fingerprints);
+ }
}
diff --git a/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse8.java b/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse8.java
index e8f0337..f4e31ff 100644
--- a/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse8.java
+++ b/fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse8.java
@@ -1,5 +1,10 @@
package org.fastfilter.xor;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -321,4 +326,25 @@ public static XorBinaryFuse8 deserialize(ByteBuffer buffer) {
return new XorBinaryFuse8(segmentCount, segmentLength, seed, fingerprints);
}
+
+ public void serialize(OutputStream out) throws IOException {
+ DataOutputStream dout = new DataOutputStream(out);
+ dout.writeInt(segmentLength);
+ dout.writeInt(segmentCountLength);
+ dout.writeLong(seed);
+ dout.writeInt(fingerprints.length);
+ dout.write(fingerprints);
+ }
+
+ public static XorBinaryFuse8 deserialize(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ final int segmentLength = din.readInt();
+ final int segmentCountLength = din.readInt();
+ final long seed = din.readLong();
+ final int len = din.readInt();
+ final byte[] fingerprints = new byte[len];
+ din.readFully(fingerprints);
+ final int segmentCount = segmentCountLength / segmentLength;
+ return new XorBinaryFuse8(segmentCount, segmentLength, seed, fingerprints);
+ }
}
diff --git a/fastfilter/src/test/java/org/fastfilter/TestFilterType.java b/fastfilter/src/test/java/org/fastfilter/TestFilterType.java
index bf3b172..5a2fb6b 100644
--- a/fastfilter/src/test/java/org/fastfilter/TestFilterType.java
+++ b/fastfilter/src/test/java/org/fastfilter/TestFilterType.java
@@ -11,6 +11,7 @@
import org.fastfilter.gcs.GolombCompressedSet;
import org.fastfilter.gcs.GolombCompressedSet2;
import org.fastfilter.mphf.MPHFilter;
+import org.fastfilter.ribbon.RibbonNaiveFilter;
import org.fastfilter.xor.*;
import org.fastfilter.xorplus.XorPlus8;
@@ -90,6 +91,12 @@ public Filter construct(long[] keys, int setting) {
return XorBinaryFuse32.construct(keys);
}
},
+ RIBBON_NAIVE {
+ @Override
+ public Filter construct(long[] keys, int setting) {
+ return RibbonNaiveFilter.construct(keys);
+ }
+ },
CUCKOO_8 {
@Override
public Filter construct(long[] keys, int setting) {
diff --git a/fastfilter/src/test/java/org/fastfilter/ribbon/RibbonNaiveFilterTest.java b/fastfilter/src/test/java/org/fastfilter/ribbon/RibbonNaiveFilterTest.java
new file mode 100644
index 0000000..6b1b9ad
--- /dev/null
+++ b/fastfilter/src/test/java/org/fastfilter/ribbon/RibbonNaiveFilterTest.java
@@ -0,0 +1,52 @@
+package org.fastfilter.ribbon;
+
+import static org.junit.Assert.assertTrue;
+
+import org.fastfilter.utils.RandomGenerator;
+import org.junit.Test;
+
+public class RibbonNaiveFilterTest {
+
+ @Test
+ public void noFalseNegativesSmall() {
+ assertNoFalseNegatives(10);
+ assertNoFalseNegatives(1_000);
+ }
+
+ @Test
+ public void noFalseNegativesLarge() {
+ assertNoFalseNegatives(1_000_000);
+ }
+
+ @Test
+ public void falsePositiveRateNearExpected() {
+ int len = 1_000_000;
+ long[] list = new long[len * 2];
+ RandomGenerator.createRandomUniqueListFast(list, 0);
+ long[] keys = new long[len];
+ long[] nonKeys = new long[len];
+ for (int i = 0; i < len; i++) {
+ keys[i] = list[i];
+ nonKeys[i] = list[i + len];
+ }
+ RibbonNaiveFilter filter = RibbonNaiveFilter.construct(keys);
+ int falsePositives = 0;
+ for (long nonKey : nonKeys) {
+ if (filter.mayContain(nonKey)) {
+ falsePositives++;
+ }
+ }
+ double fpp = (double) falsePositives / len;
+ // expected ~= 1/256 ~= 0.0039; generous margin to avoid flakiness
+ assertTrue("fpp too high: " + fpp, fpp < 0.01);
+ }
+
+ private void assertNoFalseNegatives(int len) {
+ long[] keys = new long[len];
+ RandomGenerator.createRandomUniqueListFast(keys, 0);
+ RibbonNaiveFilter filter = RibbonNaiveFilter.construct(keys);
+ for (long key : keys) {
+ assertTrue("key " + key + " should be present", filter.mayContain(key));
+ }
+ }
+}
diff --git a/fastfilter/src/test/java/org/fastfilter/xor/SerializationTest.java b/fastfilter/src/test/java/org/fastfilter/xor/SerializationTest.java
index df6a204..2ce1d4e 100644
--- a/fastfilter/src/test/java/org/fastfilter/xor/SerializationTest.java
+++ b/fastfilter/src/test/java/org/fastfilter/xor/SerializationTest.java
@@ -5,10 +5,15 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.Function;
import org.fastfilter.Filter;
+import org.fastfilter.ribbon.RibbonNaiveFilter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -20,28 +25,39 @@ public class SerializationTest {
private final String filterName;
private final Function constructor;
private final Function deserializer;
+ private final StreamDeserializer streamDeserializer;
public SerializationTest(String filterName,
Function constructor,
- Function deserializer) {
+ Function deserializer,
+ StreamDeserializer streamDeserializer) {
this.filterName = filterName;
this.constructor = constructor;
this.deserializer = deserializer;
+ this.streamDeserializer = streamDeserializer;
}
@Parameters(name = "{0}")
public static List