eu.mais_h.mathsync.Ibf Maven / Gradle / Ivy
The newest version!
package eu.mais_h.mathsync;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONTokener;
import eu.mais_h.mathsync.digest.Digester;
class Ibf implements Summary {
private final Bucket[] buckets;
private final BucketSelector selector;
private final Digester digester;
Ibf(JSONTokener tokener, Digester digester, BucketSelector selector) {
this(bucketsFromJSON(tokener), digester, selector);
}
Ibf(int size, Digester digester, BucketSelector selector) {
this(bucketsOfSize(size), digester, selector);
}
private Ibf(Bucket[] buckets, Digester digester, BucketSelector selector) {
if (buckets == null) {
throw new IllegalArgumentException("Buckets cannot be null");
}
if (selector == null) {
throw new IllegalArgumentException("Bucket selector cannot be null");
}
if (digester == null) {
throw new IllegalArgumentException("Digester cannot be null");
}
this.buckets = buckets;
this.selector = selector;
this.digester = digester;
}
@Override
public String toJSON() {
JSONArray array = new JSONArray();
for (Bucket b : buckets) {
array.put(b.toJSON());
}
return array.toString();
}
@Override
public Difference toDifference() {
Bucket[] copy = copyBuckets();
Set added = new HashSet<>();
Set removed = new HashSet<>();
// Search for unary buckets until there is nothing to do
boolean found = true;
while (found) {
found = false;
for (Bucket b : copy) {
int items = b.items();
if (items == 1 || items == -1) {
byte[] verified = verify(b);
if (verified != null) {
switch (items) {
case 1:
added.add(verified);
break;
case -1:
removed.add(verified);
break;
}
modifyWithSideEffect(copy, -items, verified);
found = true;
}
}
}
}
// If some buckets are not empty, there was not enough information to deserialize
for (Bucket b : copy) {
if (!b.isEmpty()) {
return null;
}
}
return new SerializedDifference(added, removed);
}
private byte[] verify(Bucket b) {
byte[] content = b.xored();
while (true) {
if (Arrays.equals(digester.digest(content), b.hashed())) {
return content;
}
if (content.length > 0 && content[content.length - 1] == (byte)0) {
content = Arrays.copyOf(content, content.length - 1);
} else {
return null;
}
}
}
@Override
public Summary plus(byte[] content) {
if (content == null) {
throw new IllegalArgumentException("Cannot add a null item to an IBF");
}
Bucket[] updated = copyBuckets();
modifyWithSideEffect(updated, 1, content);
return new Ibf(updated, digester, selector);
}
@Override
public Summary plus(Iterator items) {
if (items == null) {
throw new IllegalArgumentException("Cannot add a null iterator of items to an IBF");
}
Bucket[] updated = copyBuckets();
modifyManyWithSideEffect(updated, 1, items);
return new Ibf(updated, digester, selector);
}
@Override
public Summary minus(byte[] content) {
if (content == null) {
throw new IllegalArgumentException("Cannot remove a null item to an IBF");
}
Bucket[] updated = copyBuckets();
modifyWithSideEffect(updated, -1, content);
return new Ibf(updated, digester, selector);
}
@Override
public Summary minus(Iterator items) {
if (items == null) {
throw new IllegalArgumentException("Cannot remove a null iterator of items to an IBF");
}
Bucket[] updated = copyBuckets();
modifyManyWithSideEffect(updated, -1, items);
return new Ibf(updated, digester, selector);
}
private Ibf modifyWithIbf(int variation, Ibf other) {
if (buckets.length != other.buckets.length) {
throw new IllegalArgumentException("Cannot substract IBFs of different sizes, tried to substract " + other + " from " + this);
}
Bucket[] updated = new Bucket[buckets.length];
for (int i = 0; i < buckets.length; i++) {
Bucket otherBucket = other.buckets[i];
updated[i] = buckets[i].modify(variation * otherBucket.items(), otherBucket.xored(), otherBucket.hashed());
}
return new Ibf(updated, digester, selector);
}
private void modifyManyWithSideEffect(Bucket[] buckets, int variation, Iterator items) {
while (items.hasNext()) {
modifyWithSideEffect(buckets, variation, items.next());
}
}
/**
* Modifies an array of buckets.
*
* This method has side effects on the given array so {@link #buckets} must never ever
* be passed to this method, only copies of it obtained through {@link #copyBuckets()}.
*
* @param buckets the array of buckets to modify.
* @param variation the variation to apply.
* @param item the item to add or remove from buckets.
*/
private void modifyWithSideEffect(Bucket[] buckets, int variation, byte[] item) {
byte[] hashed = digester.digest(item);
for (int bucket : selector.selectBuckets(buckets.length, item)) {
bucket = bucket % buckets.length;
buckets[bucket] = buckets[bucket].modify(variation, item, hashed);
}
}
private Bucket[] copyBuckets() {
return Arrays.copyOf(buckets, buckets.length);
}
@Override
public final String toString() {
StringBuilder builder = new StringBuilder("IBF with buckets [");
boolean first = true;
for (Bucket bucket : buckets) {
if (first) {
first = false;
} else {
builder.append(", ");
}
builder.append(bucket);
}
return builder.append("]").toString();
}
private static Bucket[] bucketsOfSize(int size) {
if (size < 1) {
throw new IllegalArgumentException("IBF size must be a strictly positive number, given: " + size);
}
Bucket[] buckets = new Bucket[size];
for (int i = 0; i < size; i++) {
buckets[i] = Bucket.EMPTY_BUCKET;
}
return buckets;
}
private static Bucket[] bucketsFromJSON(JSONTokener tokener) {
JSONArray deserialized = new JSONArray(tokener);
Bucket[] buckets = new Bucket[deserialized.length()];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new Bucket(deserialized.getJSONArray(i));
}
return buckets;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy