/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support;
import freenet.support.io.Closer;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
/**
* @author sdiz
*/
public class CountingBloomFilter extends BloomFilter {
private boolean warnOnRemoveFromEmpty;
public void setWarnOnRemoveFromEmpty() {
warnOnRemoveFromEmpty = true;
}
/**
* Constructor
*
* @param length
* length in bits
*/
public CountingBloomFilter(int length, int k) {
super(length, k);
filter = ByteBuffer.allocate(this.length / 4);
}
/**
* Constructor
*
* @param file
* disk file
* @param length
* length in bits
* @throws IOException
*/
protected CountingBloomFilter(File file, int length, int k) throws IOException {
super(length, k);
int fileLength = length / 4;
if (!file.exists() || file.length() != fileLength)
needRebuild = true;
RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel channel = null;
try {
raf.setLength(fileLength);
channel = raf.getChannel();
filter = channel.map(MapMode.READ_WRITE, 0, fileLength).load();
} finally {
Closer.close(raf);
Closer.close(channel);
}
}
public CountingBloomFilter(int length, int k, byte[] buffer) {
super(length, k);
assert(buffer.length == length / 4);
filter = ByteBuffer.wrap(buffer);
}
@Override
public boolean getBit(int offset) {
byte b = filter.get(offset / 4);
byte v = (byte) ((b >>> offset % 4 * 2) & 3);
return v != 0;
}
@Override
public void setBit(int offset) {
byte b = filter.get(offset / 4);
byte v = (byte) ((b >>> offset % 4 * 2) & 3);
if (v == 3)
return; // overflow
b &= ~(3 << offset % 4 * 2); // unset bit
b |= (v + 1) << offset % 4 * 2; // set bit
filter.put(offset / 4, b);
}
@Override
public void unsetBit(int offset) {
byte b = filter.get(offset / 4);
byte v = (byte) ((b >>> offset % 4 * 2) & 3);
if (v == 0 && warnOnRemoveFromEmpty)
Logger.error(this, "Unsetting bit but already unset - probable double remove, can cause false negatives, is very bad!", new Exception("error"));
if (v == 0 || v == 3)
return; // overflow / underflow
b &= ~(3 << offset % 4 * 2); // unset bit
b |= (v - 1) << offset % 4 * 2; // set bit
filter.put(offset / 4, b);
}
@Override
public void fork(int k) {
lock.writeLock().lock();
try {
File tempFile = File.createTempFile("bloom-", ".tmp");
tempFile.deleteOnExit();
forkedFilter = new CountingBloomFilter(tempFile, length, k);
} catch (IOException e) {
forkedFilter = new CountingBloomFilter(length, k);
} finally {
lock.writeLock().unlock();
}
}
}